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 +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +5 -6
- data/lib/generators/goodsorcery/install/templates/initializer.rb +1 -2
- data/lib/goodsorcery/client.rb +3 -8
- data/lib/goodsorcery/configuration.rb +21 -10
- data/lib/goodsorcery/defaults.rb +1 -1
- data/lib/goodsorcery/delivery.rb +3 -12
- data/lib/goodsorcery/errors.rb +0 -4
- data/lib/goodsorcery/integrations/active_job.rb +0 -1
- data/lib/goodsorcery/integrations/rails/error_subscriber.rb +1 -5
- data/lib/goodsorcery/integrations.rb +0 -6
- data/lib/goodsorcery/performance/agent.rb +3 -4
- data/lib/goodsorcery/status_report.rb +1 -2
- data/lib/goodsorcery/version.rb +1 -1
- metadata +1 -4
- data/lib/goodsorcery/delivery/active_job_queue.rb +0 -71
- data/lib/goodsorcery/delivery/custom.rb +0 -27
- data/lib/goodsorcery/delivery_job.rb +0 -26
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: edbecd397465b4b9853689dcaeb10b832770545b51c4801f3f3dd22d6c126b08
|
|
4
|
+
data.tar.gz: fd3b3fdf18956d5e9dd9db3af4e042a5385459f531ad6791851551949590ab6c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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` |
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
data/lib/goodsorcery/client.rb
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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) ||
|
|
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
|
|
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
|
|
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, :
|
|
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=#{
|
|
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,
|
|
204
|
-
|
|
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
|
data/lib/goodsorcery/defaults.rb
CHANGED
|
@@ -37,7 +37,7 @@ module GoodSorcery
|
|
|
37
37
|
].freeze
|
|
38
38
|
|
|
39
39
|
# Transactions (exact names or regexps) that performance monitoring
|
|
40
|
-
# never records.
|
|
40
|
+
# never records.
|
|
41
41
|
PERFORMANCE_IGNORE_TRANSACTIONS = ["Rails::HealthController#show"].freeze
|
|
42
42
|
|
|
43
43
|
DISABLED_ENVIRONMENTS = %w[development test].freeze
|
data/lib/goodsorcery/delivery.rb
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
data/lib/goodsorcery/errors.rb
CHANGED
|
@@ -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)
|
|
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
|
|
80
|
-
#
|
|
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 =
|
|
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
|
data/lib/goodsorcery/version.rb
CHANGED
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.
|
|
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
|