goodsorcery 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7c757cad1765947ffa3b03990f995e418f676ff2a2385d23b9576bb4aec1462c
4
- data.tar.gz: be2c7da80aa622f3d8aedf18ce6db47d0427f3740bce87b743dde3ff41117d7d
3
+ metadata.gz: edbecd397465b4b9853689dcaeb10b832770545b51c4801f3f3dd22d6c126b08
4
+ data.tar.gz: fd3b3fdf18956d5e9dd9db3af4e042a5385459f531ad6791851551949590ab6c
5
5
  SHA512:
6
- metadata.gz: 675c74e11fff3d79ff4c4ab47f78f131174b75b98f09a441cce26b274325541f3ce97c630644879de598367f863b4ea93c01719850b1ce46aac782479844a7c3
7
- data.tar.gz: 65c4954e28c02247c4b9d00123957827625516a3b218d56773d44b36315de74d9841622d96573d7cd026afcd218949708ca93cb44510d408932b4d8d28aeb81c
6
+ metadata.gz: dcb53d171bd4f36a48c7208a9cd2b3950bd1be25741b9946945da0db0cba745b75ee8159a6058a12288fda63258d5cdcd3fe413d25f538c9c7d7601592c2fbb8
7
+ data.tar.gz: 9767d439220b934647dc06cb805fca3d0ba808f97438cb320efc3749e220e0b9d7487fc613cfbb711781a795029cd9b0cfd834d559d2417d79af8f65a176819b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.3.0 (2026-11-07)
4
+
5
+ - The `:active_job` delivery: `GoodSorcery::DeliveryJob`, `config.active_job_queue` and `GoodSorcery::RetryableDeliveryError`. Use `:async` (the default). Errors raised in your own jobs are still reported, and jobs are still measured by performance monitoring.
6
+ - Custom delivery objects (`config.delivery = <object responding to #deliver>`).
7
+ - `config.delivery` now accepts only `:async`, `:sync` and `:test`; anything else, including `:active_job` or an object, raises `ArgumentError` from `GoodSorcery.configure` with a message saying what to use instead. Setting `config.active_job_queue` raises `NoMethodError`.
8
+
3
9
  ## 0.2.0 (2026-11-07)
4
10
 
5
11
  - **Performance monitoring** (opt-in, `config.performance_monitoring = true` or `GOODSORCERY_PERFORMANCE_MONITORING=1`). Records every Rails request and every Active Job and Sidekiq job: count, failures, total time and a shared log-scale histogram (`gs-v1`) with database, external HTTP, view and Ruby time per bucket, plus queue wait for jobs. Metrics are aggregated per transaction per minute in process and sent once a minute to `POST /api/v1/apm/metrics` from a background thread (`goodsorcery-apm`).
data/README.md CHANGED
@@ -99,14 +99,13 @@ Change settings inside `GoodSorcery.configure`: it recompiles the filters and re
99
99
  | `send_job_arguments` | `true` | `false` never sends Active Job, mailer or Sidekiq arguments. |
100
100
  | `install_check` | `:verify` | `:verify`, `:test_event` or `false`; see [Install check](#install-check). |
101
101
  | `logger` | `Logger.new($stderr)` at `WARN` (`Rails.logger` in Rails) | The gem's own messages, prefixed `[goodsorcery]`. |
102
- | `delivery` | `:async` | See [Delivery](#delivery). |
102
+ | `delivery` | `:async` | `:async`, `:sync` or `:test`; see [Delivery](#delivery). |
103
103
  | `batch_size` | `25` | Events per request, 1–100. |
104
104
  | `flush_interval` | `2.0` s | Longest an event waits in the queue. |
105
105
  | `max_queue_size` | `1_000` | Events beyond this are dropped and counted. |
106
106
  | `max_retries` | `3` | For `5xx`/network failures (exponential backoff with jitter, capped at 60 s). |
107
107
  | `shutdown_timeout` | `5` s | How long the exit hook waits for the queue to drain. |
108
108
  | `open_timeout` / `read_timeout` | `2` s / `5` s | HTTP timeouts (writes use `read_timeout`). |
109
- | `active_job_queue` | `"default"` | Queue for `delivery = :active_job`. |
110
109
  | `performance_monitoring` | `false` (or `ENV["GOODSORCERY_PERFORMANCE_MONITORING"]` `1`/`0`) | Records every request and job and sends metrics once a minute; see [Performance monitoring](#performance-monitoring). Read when the app boots. |
111
110
  | `performance_traces` | `true` | Also sends the timeline of the slowest request per transaction per minute. `false` sends metrics only. |
112
111
  | `performance_ignore_transactions` | `["Rails::HealthController#show"]` | Transaction names (exact strings) or regexps never recorded. |
@@ -116,10 +115,10 @@ Change settings inside `GoodSorcery.configure`: it recompiles the filters and re
116
115
  | `config.delivery` | What it does | When to use |
117
116
  |---|---|---|
118
117
  | `:async` (default) | Pushes events onto a bounded in-memory queue. One background thread (`goodsorcery-worker`) sends them to `POST /api/v1/events/batch` when `batch_size` events are waiting or `flush_interval` has passed. Restarts after `fork`; drains at exit. | Web apps and job workers (almost everyone). |
119
- | `:sync` | Sends each event immediately in the calling thread. | Scripts, rake tasks, serverless functions, debugging. |
120
- | `:active_job` | Enqueues `GoodSorcery::DeliveryJob` with the filtered event as JSON; the job sends it and retries `429`/`5xx` (5 attempts). Falls back to `:async` if enqueueing fails. **The filtered event is stored in your job backend.** | Apps that want delivery to survive a crash and already run a durable queue. |
118
+ | `:sync` | Sends each event in the calling thread, before `notify` returns. | Serverless functions and other short-lived processes where a background thread doesn't get to run, such as AWS Lambda (the process is frozen between invocations). **Not for normal web apps:** every report adds an HTTPS round trip to the request that hit the error. |
121
119
  | `:test` | Keeps events in memory (`GoodSorcery.test_events`). Never touches the network and ignores the environment check. | Your app's test suite. |
122
- | any object | Must respond to `deliver(event_hash)`; may respond to `flush(timeout)` and `shutdown(timeout)`. | Custom transports. |
120
+
121
+ Any other value raises an `ArgumentError` from `GoodSorcery.configure`, when the app boots. The `:active_job` delivery and custom delivery objects were removed; use `:async`.
123
122
 
124
123
  ### Environments
125
124
 
@@ -209,7 +208,7 @@ Nothing to set up beyond the initializer. The Railtie:
209
208
  - inserts `GoodSorcery::Rack::Middleware` right after `ActionDispatch::ShowExceptions`, so it sees exceptions before Rails renders the error page. Errors that a lower layer turned into a 5xx response (`env["action_dispatch.exception"]`) are reported too;
210
209
  - subscribes to `Rails.error`: unhandled errors are reported at level `error`, handled ones (`Rails.error.handle`, `Rails.error.report(handled: true)`) at their severity (`warning` by default). Set `report_handled_errors = false` to skip handled ones;
211
210
  - finds the user with the controller's `user_method` (default `current_user`, private methods too), else `Current.user`, and sends only `id`, `email`, `username` and `name`;
212
- - wraps Active Job's `perform_now` outside the job's own `rescue_from`/`retry_on`, so a job is reported once when it finally fails, not on every attempt it retries itself. `GoodSorcery::DeliveryJob` is never reported;
211
+ - wraps Active Job's `perform_now` outside the job's own `rescue_from`/`retry_on`, so a job is reported once when it finally fails, not on every attempt it retries itself;
213
212
  - wraps Action Cable channel subscribe, unsubscribe and actions; errors your channel's `rescue_from` handles are not reported;
214
213
  - records breadcrumbs from `ActiveSupport::Notifications` inside requests, jobs and channel actions: `start_processing.action_controller`, `sql.active_record` (without `SCHEMA`, `CACHE` and `TRANSACTION` queries; SQL capped at 1,000 chars, with quoted strings and numbers replaced by `?` so inlined values never leave the app), `perform.active_job` and `deliver.action_mailer`. Turn them off with `config.breadcrumbs = false`;
215
214
  - loads the `goodsorcery:verify` and `goodsorcery:test` tasks and reports exceptions that end a rake task.
@@ -27,7 +27,7 @@ GoodSorcery.configure do |config|
27
27
  # config.send_job_arguments = true # false = never send job arguments
28
28
 
29
29
  # --- Delivery --------------------------------------------------------------
30
- # config.delivery = :async # :sync, :active_job, :test or a custom object
30
+ # config.delivery = :async # :sync (serverless, e.g. AWS Lambda) or :test
31
31
  # config.batch_size = 25
32
32
  # config.flush_interval = 2.0
33
33
  # config.max_queue_size = 1_000
@@ -35,7 +35,6 @@ GoodSorcery.configure do |config|
35
35
  # config.shutdown_timeout = 5
36
36
  # config.open_timeout = 2
37
37
  # config.read_timeout = 5
38
- # config.active_job_queue = "default" # with delivery = :active_job
39
38
 
40
39
  # --- What gets reported ----------------------------------------------------
41
40
  # config.release = ENV["GIT_SHA"] # auto-detected from deploy env vars or REVISION
@@ -4,7 +4,6 @@ module GoodSorcery
4
4
  # Wires configuration to the event pipeline and the delivery. One client
5
5
  # per process, built lazily; GoodSorcery.configure rebuilds its parts.
6
6
  class Client
7
- NETWORK_DELIVERIES = %i[async sync active_job].freeze
8
7
  REPORTING_KEY = :__goodsorcery_reporting
9
8
 
10
9
  attr_reader :config, :log
@@ -74,14 +73,11 @@ module GoodSorcery
74
73
  end
75
74
 
76
75
  # Whether performance monitoring may run (DESIGN.md 8.1): with the :test
77
- # delivery, or a network delivery in an enabled environment with a key.
76
+ # delivery, or in an enabled environment with a key.
78
77
  def performance_allowed?
79
78
  return true if test_delivery?
80
- return @gate.enabled? && !config.api_key.to_s.empty? if NETWORK_DELIVERIES.include?(config.delivery)
81
79
 
82
- log.warn_once(:apm_custom_delivery,
83
- "performance monitoring needs the :async, :sync or :active_job delivery; it is off")
84
- false
80
+ @gate.enabled? && !config.api_key.to_s.empty?
85
81
  end
86
82
 
87
83
  def verify
@@ -158,7 +154,7 @@ module GoodSorcery
158
154
  end
159
155
 
160
156
  def skip?(exception)
161
- ReportedMark.reported?(exception) || exception.is_a?(RetryableDeliveryError) || ignore_list.ignore?(exception)
157
+ ReportedMark.reported?(exception) || ignore_list.ignore?(exception)
162
158
  end
163
159
 
164
160
  def test_delivery?
@@ -166,7 +162,6 @@ module GoodSorcery
166
162
  end
167
163
 
168
164
  def credentials_present?
169
- return true unless NETWORK_DELIVERIES.include?(config.delivery)
170
165
  return true unless config.api_key.to_s.empty?
171
166
 
172
167
  log.warn_once(:missing_api_key,
@@ -11,17 +11,17 @@ module GoodSorcery
11
11
  extend Validation
12
12
  include PerformanceSettings
13
13
 
14
- DELIVERIES = %i[async sync active_job test].freeze
14
+ DELIVERIES = %i[async sync test].freeze
15
15
  DELIVERY_SETTINGS = %i[
16
16
  delivery api_key endpoint batch_size flush_interval max_queue_size shutdown_timeout
17
- max_retries open_timeout read_timeout active_job_queue
17
+ max_retries open_timeout read_timeout
18
18
  ].freeze
19
19
 
20
20
  attr_writer :api_key, :environment, :release, :logger, :root
21
21
  attr_accessor :disabled_environments, :enabled_environments, :filter_params,
22
22
  :use_rails_filter_parameters, :extra_filters, :ignore_exceptions, :tags,
23
23
  :user_method, :send_code_context, :breadcrumbs,
24
- :report_handled_errors, :active_job_queue, :request_extractor,
24
+ :report_handled_errors, :request_extractor,
25
25
  :send_request_params, :send_request_headers, :send_session, :send_job_arguments
26
26
  attr_reader :delivery, :batch_size, :flush_interval, :max_queue_size,
27
27
  :shutdown_timeout, :max_retries, :open_timeout, :read_timeout,
@@ -121,9 +121,8 @@ module GoodSorcery
121
121
  # Never shows the API key (or endpoint credentials): consoles, error
122
122
  # pages and loggers print objects with #inspect.
123
123
  def inspect
124
- delivery_name = delivery.is_a?(Symbol) ? delivery.inspect : delivery.class.name
125
124
  "#<#{self.class.name} api_key=#{masked_api_key} endpoint=#{endpoint_label} " \
126
- "environment=#{environment} delivery=#{delivery_name}>"
125
+ "environment=#{environment} delivery=#{delivery.inspect}>"
127
126
  end
128
127
 
129
128
  private
@@ -146,7 +145,6 @@ module GoodSorcery
146
145
  @max_retries = 3
147
146
  @open_timeout = 2
148
147
  @read_timeout = 5
149
- @active_job_queue = "default"
150
148
  end
151
149
 
152
150
  def assign_behaviour_defaults
@@ -193,15 +191,28 @@ module GoodSorcery
193
191
  end
194
192
  end
195
193
 
196
- # Normalizes and validates config.delivery.
194
+ # Normalizes and validates config.delivery. The message names a rejected
195
+ # symbol but only the class of anything else, which may hold credentials.
197
196
  module DeliverySetting
197
+ REMOVED = {
198
+ active_job: "The :active_job delivery was removed: use :async (the default), " \
199
+ "which sends events from a background thread."
200
+ }.freeze
201
+ CUSTOM_REMOVED = "Custom delivery objects are no longer supported: use :async (the default), " \
202
+ ":sync in serverless functions or :test in your test suite."
203
+
198
204
  def self.normalize(value)
199
205
  symbol = value.is_a?(String) ? value.to_sym : value
200
206
  return symbol if Configuration::DELIVERIES.include?(symbol)
201
- return value if !value.is_a?(Symbol) && value.respond_to?(:deliver)
202
207
 
203
- raise ArgumentError, "delivery must be one of #{Configuration::DELIVERIES.map(&:inspect).join(", ")} " \
204
- "or an object responding to #deliver (got a #{value.class})"
208
+ raise ArgumentError, message(symbol, value)
209
+ end
210
+
211
+ def self.message(symbol, value)
212
+ got = symbol.is_a?(Symbol) ? symbol.inspect : "an instance of #{value.class}"
213
+ hint = symbol.is_a?(Symbol) ? REMOVED[symbol] : (CUSTOM_REMOVED if value.respond_to?(:deliver))
214
+ ["delivery must be one of #{Configuration::DELIVERIES.map(&:inspect).join(", ")} (got #{got}).", hint]
215
+ .compact.join(" ")
205
216
  end
206
217
  end
207
218
  end
@@ -37,7 +37,7 @@ module GoodSorcery
37
37
  ].freeze
38
38
 
39
39
  # Transactions (exact names or regexps) that performance monitoring
40
- # never records. GoodSorcery::DeliveryJob is always skipped as well.
40
+ # never records.
41
41
  PERFORMANCE_IGNORE_TRANSACTIONS = ["Rails::HealthController#show"].freeze
42
42
 
43
43
  DISABLED_ENVIRONMENTS = %w[development test].freeze
@@ -6,20 +6,17 @@ require_relative "delivery/worker"
6
6
  require_relative "delivery/async"
7
7
  require_relative "delivery/sync"
8
8
  require_relative "delivery/test"
9
- require_relative "delivery/custom"
10
- require_relative "delivery/active_job_queue"
11
9
 
12
10
  module GoodSorcery
13
- # Delivery strategies (config.delivery). Each exposes deliver(event),
11
+ # Delivery strategies (config.delivery: :async, :sync or :test; the
12
+ # setter rejects anything else). Each exposes deliver(event),
14
13
  # flush(timeout) and shutdown(timeout).
15
14
  module Delivery
16
15
  def self.build(config, client)
17
16
  case config.delivery
18
- when :async then async(config, client.transport, client.serializer, client.log)
19
17
  when :sync then Sync.new(transport: client.transport, serializer: client.serializer, log: client.log)
20
18
  when :test then Test.new
21
- when :active_job then active_job(config, client)
22
- else Custom.new(config.delivery)
19
+ else async(config, client.transport, client.serializer, client.log)
23
20
  end
24
21
  end
25
22
 
@@ -29,11 +26,5 @@ module GoodSorcery
29
26
  max_queue_size: config.max_queue_size, max_retries: config.max_retries,
30
27
  shutdown_timeout: config.shutdown_timeout)
31
28
  end
32
-
33
- def self.active_job(config, client)
34
- ActiveJobQueue.new(serializer: client.serializer, queue_name: config.active_job_queue, log: client.log) do
35
- async(config, client.transport, client.serializer, client.log)
36
- end
37
- end
38
29
  end
39
30
  end
@@ -7,8 +7,4 @@ module GoodSorcery
7
7
  # Raised (and rescued) by `goodsorcery:test` and the install check to send a
8
8
  # realistic test exception.
9
9
  class TestError < StandardError; end
10
-
11
- # Raised by GoodSorcery::DeliveryJob so ActiveJob retries a delivery that
12
- # failed with a 429, a 5xx or a network error.
13
- class RetryableDeliveryError < Error; end
14
10
  end
@@ -25,7 +25,6 @@ module GoodSorcery
25
25
  end
26
26
 
27
27
  def perform_now
28
- return super if Integrations.delivery_job?(self)
29
28
  return Integrations.run_unit_of_work(JobDetails.new(self)) { super } unless Performance.active?
30
29
 
31
30
  Performance.measure_job(Performance::ActiveJobTiming.new(self)) do
@@ -12,7 +12,7 @@ module GoodSorcery
12
12
 
13
13
  def report(error, handled:, severity: nil, context: {}, source: nil)
14
14
  return if handled && !GoodSorcery.configuration.report_handled_errors
15
- return if ReportedMark.reported?(error) || from_delivery_job?(context)
15
+ return if ReportedMark.reported?(error)
16
16
 
17
17
  GoodSorcery.notify(error, level: level(handled, severity),
18
18
  context: { "rails_error" => rails_error_context(context, source) })
@@ -24,10 +24,6 @@ module GoodSorcery
24
24
  handled ? LEVELS.fetch(severity.to_s.to_sym, "warning") : "error"
25
25
  end
26
26
 
27
- def from_delivery_job?(context)
28
- context.is_a?(Hash) && Integrations.delivery_job?(context[:job])
29
- end
30
-
31
27
  # Controllers and jobs from Rails' execution context become
32
28
  # "#<ClassName>"; records become GlobalID strings.
33
29
  def rails_error_context(context, source)
@@ -5,12 +5,6 @@ module GoodSorcery
5
5
  # unit of work and leaves an enricher that reads its details only when an
6
6
  # error is reported.
7
7
  module Integrations
8
- # GoodSorcery::DeliveryJob sends events; errors it raises are never
9
- # reported (no recursion).
10
- def self.delivery_job?(object)
11
- GoodSorcery.const_defined?(:DeliveryJob, false) && object.is_a?(GoodSorcery::DeliveryJob)
12
- end
13
-
14
8
  # Runs the block in a fresh scope prepared by +details+ (an object with
15
9
  # +open(scope)+), reporting any exception that escapes it, then
16
10
  # re-raising.
@@ -76,15 +76,14 @@ module GoodSorcery
76
76
  end
77
77
  end
78
78
 
79
- # performance_ignore_transactions: exact names or regexps, plus the gem's
80
- # own delivery job. Answers are cached per name.
79
+ # performance_ignore_transactions: exact names or regexps. Answers are
80
+ # cached per name.
81
81
  class IgnoreMatcher
82
- ALWAYS = ["GoodSorcery::DeliveryJob"].freeze
83
82
  MAX_CACHED = 1_000
84
83
 
85
84
  def initialize(list)
86
85
  entries = Array(list)
87
- @names = (entries.grep_v(Regexp).map(&:to_s) + ALWAYS).to_h { |name| [name, true] }
86
+ @names = entries.grep_v(Regexp).map(&:to_s).to_h { |name| [name, true] }
88
87
  @patterns = entries.grep(Regexp)
89
88
  @cache = {}
90
89
  end
@@ -43,8 +43,7 @@ module GoodSorcery
43
43
  end
44
44
 
45
45
  def delivery_label
46
- delivery = @config.delivery
47
- delivery.is_a?(Symbol) ? delivery.to_s : delivery.class.name.to_s
46
+ @config.delivery.to_s
48
47
  end
49
48
  end
50
49
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GoodSorcery
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  NOTIFIER_NAME = "goodsorcery-ruby"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: goodsorcery
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Philip Q Nguyen
@@ -36,15 +36,12 @@ files:
36
36
  - lib/goodsorcery/configuration/validation.rb
37
37
  - lib/goodsorcery/defaults.rb
38
38
  - lib/goodsorcery/delivery.rb
39
- - lib/goodsorcery/delivery/active_job_queue.rb
40
39
  - lib/goodsorcery/delivery/async.rb
41
40
  - lib/goodsorcery/delivery/backoff.rb
42
- - lib/goodsorcery/delivery/custom.rb
43
41
  - lib/goodsorcery/delivery/event_queue.rb
44
42
  - lib/goodsorcery/delivery/sync.rb
45
43
  - lib/goodsorcery/delivery/test.rb
46
44
  - lib/goodsorcery/delivery/worker.rb
47
- - lib/goodsorcery/delivery_job.rb
48
45
  - lib/goodsorcery/env_flag.rb
49
46
  - lib/goodsorcery/environment_gate.rb
50
47
  - lib/goodsorcery/errors.rb
@@ -1,71 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module GoodSorcery
4
- module Delivery
5
- # Enqueues GoodSorcery::DeliveryJob with the filtered event as a JSON
6
- # string. Falls back to the background thread when ActiveJob is not loaded
7
- # or enqueueing raises. Note: the event is stored in the job backend.
8
- class ActiveJobQueue
9
- def initialize(serializer:, queue_name:, log:, &fallback_builder)
10
- @serializer = serializer
11
- @queue_name = queue_name
12
- @log = log
13
- @fallback_builder = fallback_builder
14
- @fallback = nil
15
- @lock = Mutex.new
16
- end
17
-
18
- def deliver(event)
19
- job = DeliveryJobLoader.job_class
20
- return deliver_in_background(event, "ActiveJob is not loaded") unless job
21
-
22
- json = @serializer.dump(event)
23
- return false unless json
24
-
25
- return true if enqueued?(job.set(queue: @queue_name).perform_later(json))
26
-
27
- deliver_in_background(event, "GoodSorcery::DeliveryJob was not enqueued")
28
- rescue StandardError => e
29
- deliver_in_background(event, "enqueueing GoodSorcery::DeliveryJob failed (#{e.class})")
30
- end
31
-
32
- def flush(timeout = 5)
33
- @fallback ? @fallback.flush(timeout) : true
34
- end
35
-
36
- def shutdown(timeout = 5)
37
- @fallback ? @fallback.shutdown(timeout) : true
38
- end
39
-
40
- private
41
-
42
- # perform_later returns false when a callback aborted the enqueue or
43
- # the adapter raised ActiveJob::EnqueueError.
44
- def enqueued?(job)
45
- return false unless job
46
-
47
- job.respond_to?(:successfully_enqueued?) ? job.successfully_enqueued? != false : true
48
- end
49
-
50
- def deliver_in_background(event, reason)
51
- @log.warn_every(:active_job_fallback, 60, "#{reason}; delivering in the background thread instead")
52
- fallback.deliver(event)
53
- end
54
-
55
- def fallback
56
- @fallback || @lock.synchronize { @fallback ||= @fallback_builder.call }
57
- end
58
- end
59
-
60
- # Defines GoodSorcery::DeliveryJob on first use, once ActiveJob is loaded.
61
- module DeliveryJobLoader
62
- def self.job_class
63
- return GoodSorcery::DeliveryJob if GoodSorcery.const_defined?(:DeliveryJob, false)
64
- return nil unless defined?(::ActiveJob::Base)
65
-
66
- require_relative "../delivery_job"
67
- GoodSorcery::DeliveryJob
68
- end
69
- end
70
- end
71
- end
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module GoodSorcery
4
- module Delivery
5
- # Wraps an app-supplied delivery object. It must respond to
6
- # deliver(event_hash) and may respond to flush(timeout) and shutdown(timeout).
7
- class Custom
8
- attr_reader :target
9
-
10
- def initialize(target)
11
- @target = target
12
- end
13
-
14
- def deliver(event)
15
- @target.deliver(event)
16
- end
17
-
18
- def flush(timeout = 5)
19
- @target.respond_to?(:flush) ? @target.flush(timeout) : true
20
- end
21
-
22
- def shutdown(timeout = 5)
23
- @target.respond_to?(:shutdown) ? @target.shutdown(timeout) : true
24
- end
25
- end
26
- end
27
- end
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module GoodSorcery
4
- # Sends one already-filtered event (a JSON string) synchronously. Retries
5
- # 429/5xx/network failures with ActiveJob's retry_on. Any exception raised
6
- # here is marked as reported first, so no integration reports it back to
7
- # Good Sorcery (no recursion). The argument is captured error data, so
8
- # Active Job never writes it to the log.
9
- class DeliveryJob < ::ActiveJob::Base
10
- WAIT = ::ActiveJob.gem_version >= Gem::Version.new("7.1") ? :polynomially_longer : :exponentially_longer
11
-
12
- self.log_arguments = false
13
- queue_as { GoodSorcery.configuration.active_job_queue }
14
- retry_on GoodSorcery::RetryableDeliveryError, wait: WAIT, attempts: 5
15
-
16
- def perform(event_json)
17
- result = GoodSorcery.client.transport.post_event(event_json)
18
- return unless result.retry_later?
19
-
20
- raise GoodSorcery::RetryableDeliveryError, "event delivery failed (#{result.description})"
21
- rescue Exception => e # rubocop:disable Lint/RescueException
22
- ReportedMark.mark(e)
23
- raise
24
- end
25
- end
26
- end