solid_queue_guard 0.1.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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README.md +258 -0
  4. data/Rakefile +17 -0
  5. data/app/controllers/solid_queue_guard/application_controller.rb +6 -0
  6. data/app/controllers/solid_queue_guard/health_controller.rb +9 -0
  7. data/config/routes.rb +5 -0
  8. data/lib/generators/solid_queue_guard/install/USAGE +8 -0
  9. data/lib/generators/solid_queue_guard/install/install_generator.rb +17 -0
  10. data/lib/generators/solid_queue_guard/install/templates/solid_queue_guard.rb +17 -0
  11. data/lib/solid_queue_guard/check/result.rb +37 -0
  12. data/lib/solid_queue_guard/checks/base.rb +52 -0
  13. data/lib/solid_queue_guard/checks/config/adapter_check.rb +23 -0
  14. data/lib/solid_queue_guard/checks/config/connects_to_check.rb +38 -0
  15. data/lib/solid_queue_guard/checks/config/env_flags_check.rb +21 -0
  16. data/lib/solid_queue_guard/checks/config/process_heartbeat_config_check.rb +30 -0
  17. data/lib/solid_queue_guard/checks/config/puma_colocated_check.rb +33 -0
  18. data/lib/solid_queue_guard/checks/config/queue_database_check.rb +24 -0
  19. data/lib/solid_queue_guard/checks/config/queue_schema_check.rb +23 -0
  20. data/lib/solid_queue_guard/checks/config/scheduler_config_check.rb +32 -0
  21. data/lib/solid_queue_guard/checks/config/thread_pool_check.rb +38 -0
  22. data/lib/solid_queue_guard/checks/config/worker_coverage_check.rb +51 -0
  23. data/lib/solid_queue_guard/checks/registry.rb +48 -0
  24. data/lib/solid_queue_guard/checks/runtime/base.rb +21 -0
  25. data/lib/solid_queue_guard/checks/runtime/blocked_jobs_check.rb +9 -0
  26. data/lib/solid_queue_guard/checks/runtime/dispatcher_check.rb +9 -0
  27. data/lib/solid_queue_guard/checks/runtime/failed_jobs_check.rb +9 -0
  28. data/lib/solid_queue_guard/checks/runtime/finished_jobs_growth_check.rb +9 -0
  29. data/lib/solid_queue_guard/checks/runtime/orphaned_claims_check.rb +9 -0
  30. data/lib/solid_queue_guard/checks/runtime/paused_queue_lag_check.rb +9 -0
  31. data/lib/solid_queue_guard/checks/runtime/pidfile_check.rb +9 -0
  32. data/lib/solid_queue_guard/checks/runtime/process_topology_check.rb +9 -0
  33. data/lib/solid_queue_guard/checks/runtime/queue_lag_check.rb +9 -0
  34. data/lib/solid_queue_guard/checks/runtime/recurring_stale_check.rb +9 -0
  35. data/lib/solid_queue_guard/checks/runtime/scheduled_backlog_check.rb +9 -0
  36. data/lib/solid_queue_guard/checks/runtime/stale_process_check.rb +9 -0
  37. data/lib/solid_queue_guard/cli.rb +55 -0
  38. data/lib/solid_queue_guard/configuration.rb +33 -0
  39. data/lib/solid_queue_guard/engine.rb +21 -0
  40. data/lib/solid_queue_guard/formatters/json.rb +20 -0
  41. data/lib/solid_queue_guard/formatters/terminal.rb +43 -0
  42. data/lib/solid_queue_guard/notifiers/base.rb +17 -0
  43. data/lib/solid_queue_guard/queue_coverage.rb +37 -0
  44. data/lib/solid_queue_guard/report.rb +44 -0
  45. data/lib/solid_queue_guard/runner.rb +47 -0
  46. data/lib/solid_queue_guard/tasks.rb +39 -0
  47. data/lib/solid_queue_guard/version.rb +5 -0
  48. data/lib/solid_queue_guard.rb +38 -0
  49. metadata +346 -0
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module Checks
5
+ module Config
6
+ class SchedulerConfigCheck < Base
7
+ def call
8
+ recurring_tasks = solid_queue_configuration.send(:recurring_tasks)
9
+ scheduler_expected = solid_queue_configuration.send(:schedulers).any?
10
+
11
+ return pass('scheduler_config', 'No recurring tasks configured') if recurring_tasks.empty?
12
+
13
+ if scheduler_expected
14
+ pass('scheduler_config', "Recurring scheduler is configured (#{recurring_tasks.size} tasks)")
15
+ elsif solid_queue_configuration.send(:skip_recurring_tasks?)
16
+ warn(
17
+ 'scheduler_config',
18
+ 'Recurring tasks defined but recurring execution is skipped',
19
+ suggestion: 'Unset SOLID_QUEUE_SKIP_RECURRING and ensure scheduler process runs'
20
+ )
21
+ else
22
+ failure(
23
+ 'scheduler_config',
24
+ 'recurring.yml defines scheduled tasks but no scheduler is configured',
25
+ suggestion: 'Ensure config/recurring.yml tasks have a :schedule key and scheduler is enabled'
26
+ )
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module Checks
5
+ module Config
6
+ class ThreadPoolCheck < Base
7
+ def call
8
+ required_threads = solid_queue_configuration.send(:estimated_number_of_threads)
9
+ pool_size = SolidQueue::Record.connection_pool&.size
10
+
11
+ return skip('thread_pool', 'Queue database connection pool is not available') if pool_size.nil?
12
+
13
+ max_worker_threads = solid_queue_configuration.send(:workers_options)
14
+ .map do |options|
15
+ options.fetch(
16
+ :threads, 3
17
+ )
18
+ end.max || 1
19
+
20
+ if pool_size >= required_threads
21
+ pass(
22
+ 'thread_pool',
23
+ "Worker threads: #{max_worker_threads}, queue DB pool: #{pool_size}",
24
+ metadata: { threads: max_worker_threads, pool: pool_size, required: required_threads }
25
+ )
26
+ else
27
+ failure(
28
+ 'thread_pool',
29
+ "Worker threads: #{max_worker_threads}, queue DB pool: #{pool_size}",
30
+ suggestion: "Increase queue DB pool to at least #{required_threads} or reduce worker threads",
31
+ metadata: { threads: max_worker_threads, pool: pool_size, required: required_threads }
32
+ )
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module Checks
5
+ module Config
6
+ class WorkerCoverageCheck < Base
7
+ def call
8
+ worker_queues = worker_queue_names
9
+ required_queues = queues_requiring_workers
10
+
11
+ uncovered = QueueCoverage.uncovered_queues(required_queues, worker_queues)
12
+
13
+ if uncovered.empty?
14
+ pass('worker_coverage', 'All required queues have worker coverage')
15
+ else
16
+ failure(
17
+ 'worker_coverage',
18
+ "No workers configured for: #{uncovered.join(', ')}",
19
+ suggestion: "Add a worker for #{uncovered.join(', ')} in config/queue.yml",
20
+ metadata: { uncovered_queues: uncovered, worker_queues: worker_queues }
21
+ )
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def worker_queue_names
28
+ QueueCoverage.worker_queues_from_configuration(solid_queue_configuration)
29
+ end
30
+
31
+ def queues_requiring_workers
32
+ (recurring_queues + database_queues).uniq
33
+ end
34
+
35
+ def recurring_queues
36
+ solid_queue_configuration.recurring_tasks.filter_map(&:queue_name).uniq
37
+ rescue StandardError
38
+ []
39
+ end
40
+
41
+ def database_queues
42
+ return [] unless SolidQueue::Record.connection_pool&.connected?
43
+
44
+ SolidQueue::Job.distinct.pluck(:queue_name)
45
+ rescue StandardError
46
+ []
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module Checks
5
+ # @api private
6
+ class Registry
7
+ CONFIG_CHECKS = [
8
+ Config::AdapterCheck,
9
+ Config::QueueDatabaseCheck,
10
+ Config::ConnectsToCheck,
11
+ Config::QueueSchemaCheck,
12
+ Config::ThreadPoolCheck,
13
+ Config::WorkerCoverageCheck,
14
+ Config::SchedulerConfigCheck,
15
+ Config::EnvFlagsCheck,
16
+ Config::ProcessHeartbeatConfigCheck,
17
+ Config::PumaColocatedCheck
18
+ ].freeze
19
+
20
+ RUNTIME_CHECKS = [
21
+ Runtime::QueueLagCheck,
22
+ Runtime::StaleProcessCheck,
23
+ Runtime::ProcessTopologyCheck,
24
+ Runtime::DispatcherCheck,
25
+ Runtime::ScheduledBacklogCheck,
26
+ Runtime::BlockedJobsCheck,
27
+ Runtime::OrphanedClaimsCheck,
28
+ Runtime::FailedJobsCheck,
29
+ Runtime::RecurringStaleCheck,
30
+ Runtime::PausedQueueLagCheck,
31
+ Runtime::PidfileCheck,
32
+ Runtime::FinishedJobsGrowthCheck
33
+ ].freeze
34
+
35
+ class << self
36
+ def for(scope)
37
+ case scope.to_sym
38
+ when :config then CONFIG_CHECKS
39
+ when :runtime then RUNTIME_CHECKS
40
+ when :all then CONFIG_CHECKS + RUNTIME_CHECKS
41
+ else
42
+ raise ArgumentError, "Unknown scope: #{scope}"
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module Checks
5
+ module Runtime
6
+ class Base < Checks::Base
7
+ NOT_IMPLEMENTED_MESSAGE = 'Runtime check not implemented until v0.2'
8
+
9
+ def call
10
+ skip(check_id, NOT_IMPLEMENTED_MESSAGE)
11
+ end
12
+
13
+ private
14
+
15
+ def check_id
16
+ self.class.name.demodulize.underscore.delete_suffix('_check')
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module Checks
5
+ module Runtime
6
+ class BlockedJobsCheck < Base; end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module Checks
5
+ module Runtime
6
+ class DispatcherCheck < Base; end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module Checks
5
+ module Runtime
6
+ class FailedJobsCheck < Base; end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module Checks
5
+ module Runtime
6
+ class FinishedJobsGrowthCheck < Base; end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module Checks
5
+ module Runtime
6
+ class OrphanedClaimsCheck < Base; end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module Checks
5
+ module Runtime
6
+ class PausedQueueLagCheck < Base; end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module Checks
5
+ module Runtime
6
+ class PidfileCheck < Base; end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module Checks
5
+ module Runtime
6
+ class ProcessTopologyCheck < Base; end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module Checks
5
+ module Runtime
6
+ class QueueLagCheck < Base; end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module Checks
5
+ module Runtime
6
+ class RecurringStaleCheck < Base; end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module Checks
5
+ module Runtime
6
+ class ScheduledBacklogCheck < Base; end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module Checks
5
+ module Runtime
6
+ class StaleProcessCheck < Base; end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module CLI
5
+ module_function
6
+
7
+ def options(argv: ARGV)
8
+ {
9
+ strict: strict_flag?(argv),
10
+ format: format_flag(argv),
11
+ scope: scope_flag(argv)
12
+ }
13
+ end
14
+
15
+ def run!(scope: nil, format: nil, strict: nil, argv: ARGV)
16
+ scope ||= scope_flag(argv)
17
+ format ||= format_flag(argv)
18
+ strict = strict_flag?(argv) if strict.nil?
19
+
20
+ report = Runner.new(scope: scope).run
21
+ output = formatter_for(format).new(report).render
22
+
23
+ Rails.logger.debug output unless output.empty?
24
+
25
+ exit report.exit_code(strict: strict)
26
+ end
27
+
28
+ def formatter_for(format)
29
+ case format.to_sym
30
+ when :json then Formatters::Json
31
+ else Formatters::Terminal
32
+ end
33
+ end
34
+
35
+ def strict_flag?(argv)
36
+ SolidQueueGuard.config.strict? || argv.include?('--strict')
37
+ end
38
+
39
+ def format_flag(argv)
40
+ if (format_argument = argv.find { |argument| argument.start_with?('--format=') })
41
+ format_argument.split('=', 2).last.to_sym
42
+ else
43
+ ENV.fetch('SOLID_QUEUE_GUARD_FORMAT', 'text').to_sym
44
+ end
45
+ end
46
+
47
+ def scope_flag(argv)
48
+ if (scope_argument = argv.find { |argument| argument.start_with?('--scope=') })
49
+ scope_argument.split('=', 2).last.to_sym
50
+ else
51
+ ENV.fetch('SOLID_QUEUE_GUARD_SCOPE', 'config').to_sym
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ class Configuration
5
+ attr_accessor :enabled,
6
+ :queue_lag_thresholds,
7
+ :failed_jobs_threshold,
8
+ :stale_process_threshold,
9
+ :health_token,
10
+ :strict_mode,
11
+ :health_cache_ttl,
12
+ :scheduled_backlog_threshold,
13
+ :integrate_rails_health,
14
+ :notify_with
15
+
16
+ def initialize
17
+ @enabled = true
18
+ @queue_lag_thresholds = { default: 5.minutes }
19
+ @failed_jobs_threshold = 20
20
+ @stale_process_threshold = 5.minutes
21
+ @health_token = nil
22
+ @strict_mode = false
23
+ @health_cache_ttl = 15.seconds
24
+ @scheduled_backlog_threshold = 100
25
+ @integrate_rails_health = false
26
+ @notify_with = [:rails_logger]
27
+ end
28
+
29
+ def strict?
30
+ strict_mode || ActiveModel::Type::Boolean.new.cast(ENV.fetch('SOLID_QUEUE_GUARD_STRICT', nil))
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ class Engine < ::Rails::Engine
5
+ isolate_namespace SolidQueueGuard
6
+
7
+ rake_tasks do
8
+ load 'solid_queue_guard/tasks.rb'
9
+ end
10
+
11
+ config.solid_queue_guard = ActiveSupport::OrderedOptions.new
12
+
13
+ initializer 'solid_queue_guard.config' do
14
+ SolidQueueGuard.configure do |guard_config|
15
+ config.solid_queue_guard.each do |name, value|
16
+ guard_config.public_send("#{name}=", value)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module Formatters
5
+ # @api private
6
+ class Json
7
+ def initialize(report)
8
+ @report = report
9
+ end
10
+
11
+ def render
12
+ JSON.pretty_generate(report.to_h)
13
+ end
14
+
15
+ private
16
+
17
+ attr_reader :report
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module Formatters
5
+ # @api private
6
+ class Terminal
7
+ ICONS = { pass: '✅', warn: '⚠️', fail: '❌', skip: '⏭️' }.freeze
8
+
9
+ def initialize(report)
10
+ @report = report
11
+ end
12
+
13
+ def render
14
+ [
15
+ 'SolidQueueGuard Report',
16
+ '',
17
+ "Status: #{report.status.to_s.upcase}",
18
+ '',
19
+ 'Checks:',
20
+ *check_lines,
21
+ *suggestion_lines
22
+ ].join("\n")
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :report
28
+
29
+ def check_lines
30
+ report.results.map do |result|
31
+ icon = ICONS.fetch(result.status, '•')
32
+ "#{icon} #{result.message}"
33
+ end
34
+ end
35
+
36
+ def suggestion_lines
37
+ return [] unless report.suggestions.any?
38
+
39
+ ['', 'Suggested fixes:', *report.suggestions.map { |suggestion| "- #{suggestion}" }]
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module Notifiers
5
+ # @api private
6
+ class Base
7
+ def self.deliver(_report)
8
+ raise NotImplementedError, 'Notification adapters ship in v0.3'
9
+ end
10
+ end
11
+
12
+ class RailsLogger < Base; end
13
+ class Slack < Base; end
14
+ class Datadog < Base; end
15
+ class Webhook < Base; end
16
+ end
17
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module QueueCoverage
5
+ module_function
6
+
7
+ def worker_queues_from_configuration(configuration = SolidQueue::Configuration.new)
8
+ configuration.configured_processes
9
+ .select { |process| process.kind == :worker }
10
+ .flat_map { |process| parse_queues_option(process.attributes[:queues]) }
11
+ .uniq
12
+ end
13
+
14
+ def covers_all?(worker_queues, required_queue)
15
+ return true if worker_queues.include?('*')
16
+
17
+ worker_queues.include?(required_queue)
18
+ end
19
+
20
+ def uncovered_queues(required_queues, worker_queues)
21
+ required_queues.reject { |queue_name| covers_all?(worker_queues, queue_name) }
22
+ end
23
+
24
+ def parse_queues_option(value)
25
+ case value
26
+ when '*', nil
27
+ ['*']
28
+ when String
29
+ value.split(',').map(&:strip).reject(&:empty?)
30
+ when Array
31
+ value.map(&:to_s)
32
+ else
33
+ []
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ class Report
5
+ STATUSES = %i[healthy degraded unhealthy].freeze
6
+
7
+ attr_reader :results
8
+
9
+ def initialize(results)
10
+ @results = results
11
+ end
12
+
13
+ def status
14
+ return :unhealthy if results.any?(&:fail?)
15
+ return :degraded if results.any?(&:warn?)
16
+
17
+ :healthy
18
+ end
19
+
20
+ def warnings
21
+ results.select(&:warn?).map(&:message)
22
+ end
23
+
24
+ def suggestions
25
+ results.filter_map(&:suggestion).uniq
26
+ end
27
+
28
+ def exit_code(strict: false)
29
+ return 1 if results.any?(&:fail?)
30
+ return 1 if strict && results.any?(&:warn?)
31
+
32
+ 0
33
+ end
34
+
35
+ def to_h
36
+ {
37
+ status: status.to_s,
38
+ warnings: warnings,
39
+ suggestions: suggestions,
40
+ checks: results.map(&:to_h)
41
+ }
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ # @api private
5
+ class Runner
6
+ def initialize(scope: :config, enabled: SolidQueueGuard.enabled?)
7
+ @scope = scope
8
+ @enabled = enabled
9
+ end
10
+
11
+ def run
12
+ return Report.new([disabled_result]) unless enabled
13
+
14
+ results = checks.map do |check_class|
15
+ run_check(check_class)
16
+ end
17
+
18
+ Report.new(results)
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :scope, :enabled
24
+
25
+ def checks
26
+ Checks::Registry.for(scope)
27
+ end
28
+
29
+ def run_check(check_class)
30
+ check_class.call
31
+ rescue StandardError => e
32
+ Check::Result.new(
33
+ id: check_class.name.demodulize.underscore,
34
+ status: :fail,
35
+ message: "Check raised an error: #{e.class}: #{e.message}"
36
+ )
37
+ end
38
+
39
+ def disabled_result
40
+ Check::Result.new(
41
+ id: 'disabled',
42
+ status: :skip,
43
+ message: 'SolidQueueGuard is disabled'
44
+ )
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :solid_queue_guard do
4
+ desc 'Install Solid Queue Guard'
5
+ task install: :environment do
6
+ Rails::Command.invoke :generate, ['solid_queue_guard:install']
7
+ end
8
+
9
+ desc 'Run configuration and runtime diagnostics. Usage: doctor[json] or SOLID_QUEUE_GUARD_FORMAT=json'
10
+ task :doctor, [:format] => :environment do |_task, args|
11
+ SolidQueueGuard::CLI.run!(
12
+ scope: :config,
13
+ format: cli_format(args[:format]),
14
+ strict: cli_strict
15
+ )
16
+ end
17
+
18
+ desc 'Print machine-readable health status'
19
+ task health: :environment do
20
+ SolidQueueGuard::CLI.run!(scope: :config, format: :json, strict: cli_strict)
21
+ end
22
+
23
+ desc 'Print full diagnostic report. Usage: report[json]'
24
+ task :report, [:format] => :environment do |_task, args|
25
+ SolidQueueGuard::CLI.run!(
26
+ scope: :all,
27
+ format: cli_format(args[:format]),
28
+ strict: cli_strict
29
+ )
30
+ end
31
+
32
+ def cli_format(argument)
33
+ (argument.presence || ENV.fetch('SOLID_QUEUE_GUARD_FORMAT', 'text')).to_sym
34
+ end
35
+
36
+ def cli_strict
37
+ SolidQueueGuard.config.strict?
38
+ end
39
+ end