Skip to content

Commit 37321aa

Browse files
committed
Allow GoodJob.preserve_job_records to take a lambda that is callable after each job executes
1 parent ba50e07 commit 37321aa

File tree

5 files changed

+43
-22
lines changed

5 files changed

+43
-22
lines changed

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ Available configuration options are:
309309
- `cleanup_interval_seconds` (integer) Number of seconds a Scheduler will wait before cleaning up preserved jobs. Defaults to `600` (10 minutes). Disable with `false`. Can also be set with the environment variable `GOOD_JOB_CLEANUP_INTERVAL_SECONDS` and disabled with `0`).
310310
- `inline_execution_respects_schedule` (boolean) Opt-in to future behavior of inline execution respecting scheduled jobs. Defaults to `false`.
311311
- `logger` ([Rails Logger](https://api.rubyonrails.org/classes/ActiveSupport/Logger.html)) lets you set a custom logger for GoodJob. It should be an instance of a Rails `Logger` (Default: `Rails.logger`).
312-
- `preserve_job_records` (boolean) keeps job records in your database even after jobs are completed. (Default: `true`)
312+
- `preserve_job_records` (boolean, symbol, or lambda) keeps job records in your database even after jobs are completed. If set to `true`, all job records are preserved. If set to `:on_unhandled_error`, only jobs that finished with an unhandled error are preserved. If set to a lambda, the lambda will be called with the error_event (e.g., `:discarded`, `:retry_stopped`, or `:unhandled`) and should return a boolean indicating whether to preserve the job. (Default: `true`)
313313
- `advisory_lock_heartbeat` (boolean) whether to use an advisory lock for the purpose of determining whether an execeution process is active. (Default `true` in Development; `false` in other environments)
314314
- `retry_on_unhandled_error` (boolean) causes jobs to be re-queued and retried if they raise an instance of `StandardError`. Be advised this may lead to jobs being repeated infinitely ([see below for more on retries](#retries)). Instances of `Exception`, like SIGINT, will *always* be retried, regardless of this attribute’s value. (Default: `false`)
315315
- `on_thread_error` (proc, lambda, or callable) will be called when there is an Exception. It can be useful for logging errors to bug tracking services, like Sentry or Airbrake. Example:
@@ -375,7 +375,7 @@ GoodJob.active_record_parent_class = "ApplicationRecord"
375375
The following options are also configurable via accessors, but you are encouraged to use the configuration attributes instead because these may be deprecated and removed in the future:
376376
377377
- **`GoodJob.logger`** ([Rails Logger](https://api.rubyonrails.org/classes/ActiveSupport/Logger.html)) lets you set a custom logger for GoodJob. It should be an instance of a Rails `Logger`.
378-
- **`GoodJob.preserve_job_records`** (boolean) keeps job records in your database even after jobs are completed. (Default: `true`)
378+
- **`GoodJob.preserve_job_records`** (boolean, symbol, or lambda) keeps job records in your database even after jobs are completed. If set to `true`, all job records are preserved. If set to `:on_unhandled_error`, only jobs that finished with an unhandled error are preserved. If set to a lambda, the lambda will be called with the error_event (e.g., `:discarded`, `:retry_stopped`, or `:unhandled`) and should return a boolean indicating whether to preserve the job. (Default: `true`)
379379
- **`GoodJob.retry_on_unhandled_error`** (boolean) causes jobs to be re-queued and retried if they raise an instance of `StandardError`. Be advised this may lead to jobs being repeated infinitely ([see below for more on retries](#retries)). Instances of `Exception`, like SIGINT, will *always* be retried, regardless of this attribute’s value. (Default: `false`)
380380
- **`GoodJob.on_thread_error`** (proc, lambda, or callable) will be called when there is an Exception. It can be useful for logging errors to bug tracking services, like Sentry or Airbrake.
381381
@@ -1366,7 +1366,11 @@ To instead delete job records immediately after they are finished:
13661366
13671367
```ruby
13681368
# config/initializers/good_job.rb
1369-
config.good_job.preserve_job_records = false # defaults to true; can also be `false` or `:on_unhandled_error`
1369+
config.good_job.preserve_job_records = false # defaults to true; can also be `false`, `:on_unhandled_error`, or a lambda that takes error_event argument
1370+
1371+
# Example of using a lambda to preserve only discarded jobs
1372+
config.good_job.preserve_job_records = ->(error_event) { error_event == :discarded }
1373+
13701374
```
13711375
13721376
GoodJob will automatically delete preserved job records after 14 days. The retention period, as well as the frequency GoodJob checks for deletable records can be configured:

app/models/concerns/good_job/error_events.rb

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,21 @@ module GoodJob
55
module ErrorEvents
66
extend ActiveSupport::Concern
77

8+
INTERRUPTED = :interrupted
9+
UNHANDLED = :unhandled
10+
HANDLED = :handled
11+
RETRIED = :retried
12+
RETRY_STOPPED = :retry_stopped
13+
DISCARDED = :discarded
14+
815
included do
916
error_event_enum = {
10-
interrupted: 0,
11-
unhandled: 1,
12-
handled: 2,
13-
retried: 3,
14-
retry_stopped: 4,
15-
discarded: 5,
17+
INTERRUPTED => 0,
18+
UNHANDLED => 1,
19+
HANDLED => 2,
20+
RETRIED => 3,
21+
RETRY_STOPPED => 4,
22+
DISCARDED => 5,
1623
}
1724
if Gem::Version.new(Rails.version) >= Gem::Version.new('7.1.0.a')
1825
enum :error_event, error_event_enum, validate: { allow_nil: true }, scopes: false

app/models/good_job/job.rb

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -612,13 +612,13 @@ def perform(lock_id:)
612612

613613
interrupt_error_string = self.class.format_error(GoodJob::InterruptError.new("Interrupted after starting perform at '#{existing_performed_at}'"))
614614
self.error = interrupt_error_string
615-
self.error_event = :interrupted
615+
self.error_event = ErrorEvents::INTERRUPTED
616616
monotonic_duration = (::Process.clock_gettime(::Process::CLOCK_MONOTONIC) - monotonic_start).seconds
617617

618618
execution_attrs = {
619619
error: interrupt_error_string,
620620
finished_at: job_performed_at,
621-
error_event: :interrupted,
621+
error_event: error_event,
622622
duration: monotonic_duration,
623623
}
624624
executions.where(finished_at: nil).where.not(performed_at: nil).update_all(execution_attrs) # rubocop:disable Rails/SkipsModelValidations
@@ -655,13 +655,13 @@ def perform(lock_id:)
655655
error_event = if !handled_error
656656
nil
657657
elsif handled_error == current_thread.error_on_discard
658-
:discarded
658+
ErrorEvents::DISCARDED
659659
elsif handled_error == current_thread.error_on_retry
660-
:retried
660+
ErrorEvents::RETRIED
661661
elsif handled_error == current_thread.error_on_retry_stopped
662-
:retry_stopped
662+
ErrorEvents::RETRY_STOPPED
663663
elsif handled_error
664-
:handled
664+
ErrorEvents::HANDLED
665665
end
666666

667667
instrument_payload.merge!(
@@ -674,11 +674,11 @@ def perform(lock_id:)
674674
ExecutionResult.new(value: value, handled_error: handled_error, error_event: error_event, retried_job: current_thread.retried_job)
675675
rescue StandardError => e
676676
error_event = if e.is_a?(GoodJob::InterruptError)
677-
:interrupted
677+
ErrorEvents::INTERRUPTED
678678
elsif e == current_thread.error_on_retry_stopped
679-
:retry_stopped
679+
ErrorEvents::RETRY_STOPPED
680680
else
681-
:unhandled
681+
ErrorEvents::UNHANDLED
682682
end
683683

684684
instrument_payload.merge!(
@@ -722,8 +722,7 @@ def perform(lock_id:)
722722
end
723723

724724
assign_attributes(job_attributes)
725-
preserve_unhandled = result.unhandled_error && (GoodJob.retry_on_unhandled_error || GoodJob.preserve_job_records == :on_unhandled_error)
726-
if finished_at.blank? || GoodJob.preserve_job_records == true || reenqueued || preserve_unhandled || cron_key.present?
725+
if finished_at.blank? || cron_key.present? || preserve_job_record?(result)
727726
transaction do
728727
execution.save!
729728
save!
@@ -809,6 +808,14 @@ def active_job_data
809808
job_data["good_job_labels"] = Array(labels) if labels.present?
810809
end
811810
end
811+
812+
def preserve_job_record?(result)
813+
return true if GoodJob.preserve_job_records == true
814+
return true if result.unhandled_error && GoodJob.preserve_job_records == :on_unhandled_error
815+
return true if GoodJob.preserve_job_records.respond_to?(:call) && GoodJob.preserve_job_records.call(result.error_event)
816+
817+
false
818+
end
812819
end
813820
end
814821

lib/good_job.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,12 @@ module GoodJob
8080
# Whether to preserve job records in the database after they have finished (default: +true+).
8181
# If you want to preserve jobs for latter inspection, set this to +true+.
8282
# If you want to preserve only jobs that finished with error for latter inspection, set this to +:on_unhandled_error+.
83+
# If you want to preserve jobs based on the error event, set this to a lambda that takes the error_event argument.
8384
# If you do not want to preserve jobs, set this to +false+.
8485
# When using GoodJob's cron functionality, job records will be preserved for a brief time to prevent duplicate jobs.
85-
# @return [Boolean, Symbol, nil]
86+
# @example Preserve only jobs that were discarded
87+
# GoodJob.preserve_job_records = ->(error_event) { error_event == :discarded }
88+
# @return [Boolean, Symbol, Proc, nil]
8689
mattr_accessor :preserve_job_records, default: true
8790

8891
# @!attribute [rw] retry_on_unhandled_error

spec/app/models/good_job/job_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1047,7 +1047,7 @@ def job_params
10471047
job: good_job,
10481048
error: an_instance_of(TestJob::ExpectedError),
10491049
unhandled_error: an_instance_of(TestJob::ExpectedError),
1050-
error_event: :unhandled
1050+
error_event: GoodJob::ErrorEvents::UNHANDLED
10511051
)
10521052
end
10531053
ActiveSupport::Notifications.subscribed(callback, "perform_job.good_job") do

0 commit comments

Comments
 (0)