solid_queue_guard 0.1.0 → 1.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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +12 -0
  3. data/.rubocop.yml +44 -0
  4. data/.ruby-version +1 -0
  5. data/CHANGELOG.md +81 -0
  6. data/Gemfile +8 -0
  7. data/Gemfile.lock +308 -0
  8. data/README.md +79 -14
  9. data/app/controllers/solid_queue_guard/application_controller.rb +1 -0
  10. data/app/controllers/solid_queue_guard/health_controller.rb +22 -1
  11. data/lib/generators/solid_queue_guard/install/USAGE.ci +9 -0
  12. data/lib/generators/solid_queue_guard/install/ci_generator.rb +19 -0
  13. data/lib/generators/solid_queue_guard/install/templates/solid_queue_guard.rb +11 -1
  14. data/lib/generators/solid_queue_guard/install/templates/solid_queue_guard.yml +25 -0
  15. data/lib/solid_queue_guard/checks/base.rb +16 -0
  16. data/lib/solid_queue_guard/checks/config/async_supervisor_config_check.rb +25 -0
  17. data/lib/solid_queue_guard/checks/config/puma_colocated_check.rb +5 -11
  18. data/lib/solid_queue_guard/checks/config/topology_recommendation_check.rb +24 -0
  19. data/lib/solid_queue_guard/checks/registry.rb +9 -2
  20. data/lib/solid_queue_guard/checks/runtime/base.rb +1 -5
  21. data/lib/solid_queue_guard/checks/runtime/blocked_jobs_check.rb +20 -1
  22. data/lib/solid_queue_guard/checks/runtime/database_support.rb +40 -0
  23. data/lib/solid_queue_guard/checks/runtime/dispatcher_check.rb +26 -1
  24. data/lib/solid_queue_guard/checks/runtime/failed_jobs_check.rb +21 -1
  25. data/lib/solid_queue_guard/checks/runtime/finished_jobs_growth_check.rb +20 -1
  26. data/lib/solid_queue_guard/checks/runtime/orphaned_claims_check.rb +18 -1
  27. data/lib/solid_queue_guard/checks/runtime/paused_queue_lag_check.rb +22 -1
  28. data/lib/solid_queue_guard/checks/runtime/pidfile_check.rb +33 -1
  29. data/lib/solid_queue_guard/checks/runtime/process_topology_check.rb +31 -1
  30. data/lib/solid_queue_guard/checks/runtime/puma_plugin_runtime_check.rb +39 -0
  31. data/lib/solid_queue_guard/checks/runtime/queue_lag_check.rb +30 -1
  32. data/lib/solid_queue_guard/checks/runtime/recurring_stale_check.rb +29 -1
  33. data/lib/solid_queue_guard/checks/runtime/scheduled_backlog_check.rb +18 -1
  34. data/lib/solid_queue_guard/checks/runtime/stale_process_check.rb +19 -1
  35. data/lib/solid_queue_guard/cli.rb +10 -1
  36. data/lib/solid_queue_guard/configuration.rb +36 -1
  37. data/lib/solid_queue_guard/engine.rb +12 -0
  38. data/lib/solid_queue_guard/formatters/terminal.rb +1 -1
  39. data/lib/solid_queue_guard/health/cache.rb +28 -0
  40. data/lib/solid_queue_guard/health/payload.rb +50 -0
  41. data/lib/solid_queue_guard/http_status_policy.rb +35 -0
  42. data/lib/solid_queue_guard/metrics/exporter.rb +34 -0
  43. data/lib/solid_queue_guard/metrics/open_telemetry.rb +19 -0
  44. data/lib/solid_queue_guard/metrics/prometheus.rb +21 -0
  45. data/lib/solid_queue_guard/metrics/statsd.rb +25 -0
  46. data/lib/solid_queue_guard/notifier.rb +76 -0
  47. data/lib/solid_queue_guard/notifiers/base.rb +2 -7
  48. data/lib/solid_queue_guard/puma_plugin_support.rb +35 -0
  49. data/lib/solid_queue_guard/recommendations/topology.rb +76 -0
  50. data/lib/solid_queue_guard/runner.rb +10 -1
  51. data/lib/solid_queue_guard/tasks.rb +1 -1
  52. data/lib/solid_queue_guard/version.rb +1 -1
  53. data/lib/solid_queue_guard.rb +1 -1
  54. data/solid_queue_guard.gemspec +54 -0
  55. metadata +25 -1
@@ -3,7 +3,25 @@
3
3
  module SolidQueueGuard
4
4
  module Checks
5
5
  module Runtime
6
- class StaleProcessCheck < Base; end
6
+ class StaleProcessCheck < Base
7
+ def call
8
+ with_queue_database do
9
+ threshold = config.check_setting(:stale_process, :threshold, config.stale_process_threshold)
10
+ stale = SolidQueue::Process.where(last_heartbeat_at: ...threshold.ago)
11
+
12
+ if stale.none?
13
+ pass(check_id, 'All Solid Queue processes have recent heartbeats')
14
+ else
15
+ failure(
16
+ check_id,
17
+ "#{stale.count} Solid Queue process(es) have stale heartbeats",
18
+ suggestion: 'Restart the Solid Queue supervisor and verify workers are running',
19
+ metadata: { stale_processes: stale.count }
20
+ )
21
+ end
22
+ end
23
+ end
24
+ end
7
25
  end
8
26
  end
9
27
  end
@@ -18,9 +18,12 @@ module SolidQueueGuard
18
18
  strict = strict_flag?(argv) if strict.nil?
19
19
 
20
20
  report = Runner.new(scope: scope).run
21
+ Notifier.deliver_all(report) if notify?(report)
22
+ Metrics::Exporter.export(report) if SolidQueueGuard.config.metrics_backends.any?
23
+
21
24
  output = formatter_for(format).new(report).render
22
25
 
23
- Rails.logger.debug output unless output.empty?
26
+ $stdout.puts(output) unless output.empty?
24
27
 
25
28
  exit report.exit_code(strict: strict)
26
29
  end
@@ -51,5 +54,11 @@ module SolidQueueGuard
51
54
  ENV.fetch('SOLID_QUEUE_GUARD_SCOPE', 'config').to_sym
52
55
  end
53
56
  end
57
+
58
+ def notify?(report)
59
+ return false unless SolidQueueGuard.config.notify_with.any?
60
+
61
+ report.status != :healthy
62
+ end
54
63
  end
55
64
  end
@@ -11,7 +11,12 @@ module SolidQueueGuard
11
11
  :health_cache_ttl,
12
12
  :scheduled_backlog_threshold,
13
13
  :integrate_rails_health,
14
- :notify_with
14
+ :notify_with,
15
+ :metrics_backends,
16
+ :disabled_checks,
17
+ :checks,
18
+ :degraded_http_status,
19
+ :unhealthy_http_status
15
20
 
16
21
  def initialize
17
22
  @enabled = true
@@ -24,10 +29,40 @@ module SolidQueueGuard
24
29
  @scheduled_backlog_threshold = 100
25
30
  @integrate_rails_health = false
26
31
  @notify_with = [:rails_logger]
32
+ @metrics_backends = []
33
+ @disabled_checks = []
34
+ @checks = ActiveSupport::OrderedOptions.new
35
+ @degraded_http_status = :ok
36
+ @unhealthy_http_status = :service_unavailable
27
37
  end
28
38
 
29
39
  def strict?
30
40
  strict_mode || ActiveModel::Type::Boolean.new.cast(ENV.fetch('SOLID_QUEUE_GUARD_STRICT', nil))
31
41
  end
42
+
43
+ def check_enabled?(check_id)
44
+ id = check_id.to_sym
45
+ return false if disabled_checks.map(&:to_sym).include?(id)
46
+
47
+ settings = check_settings_for(id)
48
+ return true if settings.nil?
49
+
50
+ enabled_value = settings[:enabled]
51
+ enabled_value = settings['enabled'] if enabled_value.nil?
52
+ enabled_value != false
53
+ end
54
+
55
+ def check_settings_for(check_id)
56
+ checks[check_id.to_sym] || checks[check_id.to_s]
57
+ end
58
+
59
+ def check_setting(check_id, key, default = nil)
60
+ settings = check_settings_for(check_id)
61
+ return default unless settings
62
+
63
+ value = settings[key]
64
+ value = settings[key.to_sym] if value.nil?
65
+ value.nil? ? default : value
66
+ end
32
67
  end
33
68
  end
@@ -17,5 +17,17 @@ module SolidQueueGuard
17
17
  end
18
18
  end
19
19
  end
20
+
21
+ initializer 'solid_queue_guard.rails_health' do
22
+ next unless SolidQueueGuard.config.integrate_rails_health
23
+
24
+ ActiveSupport.on_load(:action_controller_base) do
25
+ Rails::HealthController.class_eval do
26
+ def solid_queue_guard_status
27
+ SolidQueueGuard::Health::Cache.fetch[:status]
28
+ end
29
+ end
30
+ end
31
+ end
20
32
  end
21
33
  end
@@ -27,7 +27,7 @@ module SolidQueueGuard
27
27
  attr_reader :report
28
28
 
29
29
  def check_lines
30
- report.results.map do |result|
30
+ report.results.reject(&:skip?).map do |result|
31
31
  icon = ICONS.fetch(result.status, '•')
32
32
  "#{icon} #{result.message}"
33
33
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module Health
5
+ class Cache
6
+ def self.fetch
7
+ new.fetch
8
+ end
9
+
10
+ def fetch
11
+ Rails.cache.fetch(cache_key, expires_in: SolidQueueGuard.config.health_cache_ttl) do
12
+ build_payload
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def cache_key
19
+ 'solid_queue_guard/health'
20
+ end
21
+
22
+ def build_payload
23
+ report = Runner.new(scope: :all).run
24
+ Payload.new(report).to_h
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module Health
5
+ class Payload
6
+ def initialize(report)
7
+ @report = report
8
+ end
9
+
10
+ def to_h
11
+ base = report.to_h.merge(
12
+ queue_lag_seconds: queue_lag_seconds,
13
+ failed_jobs_last_hour: failed_jobs_last_hour,
14
+ dead_processes: dead_processes
15
+ )
16
+
17
+ base[:recommendations] = topology_recommendations if topology_recommendations.any?
18
+ base
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :report
24
+
25
+ def queue_lag_seconds
26
+ metadata_value('queue_lag') { |metadata| metadata[:queue_lag_seconds] }
27
+ end
28
+
29
+ def failed_jobs_last_hour
30
+ metadata_value('failed_jobs') { |metadata| metadata[:failed_jobs_last_hour] } || 0
31
+ end
32
+
33
+ def dead_processes
34
+ metadata_value('stale_process') { |metadata| metadata[:stale_processes] } || 0
35
+ end
36
+
37
+ def metadata_value(check_id)
38
+ result = report.results.find { |entry| entry.id == check_id }
39
+ metadata = result&.metadata || {}
40
+ value = yield(metadata)
41
+ value unless value.nil?
42
+ end
43
+
44
+ def topology_recommendations
45
+ result = report.results.find { |entry| entry.id == 'topology_recommendation' }
46
+ result&.metadata&.fetch(:recommendations, []) || []
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ # @api private
5
+ module HttpStatusPolicy
6
+ module_function
7
+
8
+ STATUS_MAP = {
9
+ ok: :ok,
10
+ success: :ok,
11
+ 200 => :ok,
12
+ multi_status: :multi_status,
13
+ 207 => :multi_status,
14
+ service_unavailable: :service_unavailable,
15
+ unavailable: :service_unavailable,
16
+ 503 => :service_unavailable
17
+ }.freeze
18
+
19
+ def resolve(status)
20
+ key = status.is_a?(Integer) ? status : status.to_sym
21
+ STATUS_MAP.fetch(key, :ok)
22
+ end
23
+
24
+ def for_report_status(report_status, config: SolidQueueGuard.config)
25
+ case report_status.to_s
26
+ when 'unhealthy'
27
+ resolve(config.unhealthy_http_status)
28
+ when 'degraded'
29
+ resolve(config.degraded_http_status)
30
+ else
31
+ :ok
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module Metrics
5
+ class Exporter
6
+ STATUS_VALUES = { healthy: 0, degraded: 1, unhealthy: 2 }.freeze
7
+
8
+ def self.export(report, backends: SolidQueueGuard.config.metrics_backends)
9
+ new(report, backends: backends).export
10
+ end
11
+
12
+ def initialize(report, backends:)
13
+ @report = report
14
+ @backends = Array(backends)
15
+ end
16
+
17
+ def export
18
+ backends.each { |backend| export_backend(backend) }
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :report, :backends
24
+
25
+ def export_backend(backend)
26
+ case backend.to_sym
27
+ when :statsd then Statsd.export(report)
28
+ when :prometheus then Prometheus.export(report)
29
+ when :opentelemetry then OpenTelemetry.export(report)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module Metrics
5
+ module OpenTelemetry
6
+ module_function
7
+
8
+ def export(report)
9
+ return unless defined?(::OpenTelemetry)
10
+
11
+ meter = ::OpenTelemetry.meter_provider.meter('solid_queue_guard')
12
+ gauge = meter.create_gauge('solid_queue.guard.overall_status', unit: 'status')
13
+ gauge.record(Exporter::STATUS_VALUES.fetch(report.status))
14
+ rescue StandardError
15
+ nil
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module Metrics
5
+ module Prometheus
6
+ module_function
7
+
8
+ def export(report)
9
+ path = ENV.fetch('SOLID_QUEUE_GUARD_PROMETHEUS_FILE', Rails.root.join('tmp/solid_queue_guard.prom'))
10
+ File.write(path, render(report))
11
+ end
12
+
13
+ def render(report)
14
+ <<~PROM
15
+ # TYPE solid_queue_guard_overall_status gauge
16
+ solid_queue_guard_overall_status #{Exporter::STATUS_VALUES.fetch(report.status)}
17
+ PROM
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module Metrics
5
+ module Statsd
6
+ module_function
7
+
8
+ def export(report)
9
+ require 'socket'
10
+ host = ENV.fetch('SOLID_QUEUE_GUARD_STATSD_HOST', '127.0.0.1')
11
+ port = ENV.fetch('SOLID_QUEUE_GUARD_STATSD_PORT', 8125).to_i
12
+ socket = UDPSocket.new
13
+ socket.send(metric_line(report), 0, host, port)
14
+ ensure
15
+ socket&.close
16
+ end
17
+
18
+ def metric_line(report)
19
+ "solid_queue.guard.overall_status:#{STATUS_VALUES.fetch(report.status)}|g"
20
+ end
21
+
22
+ STATUS_VALUES = Exporter::STATUS_VALUES
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ class Notifier
5
+ class << self
6
+ def deliver_all(report, adapters: SolidQueueGuard.config.notify_with)
7
+ Array(adapters).each do |adapter|
8
+ deliver(adapter, report)
9
+ end
10
+ end
11
+
12
+ def deliver(adapter, report)
13
+ case adapter.to_sym
14
+ when :slack then deliver_slack(report)
15
+ when :datadog then deliver_datadog(report)
16
+ when :webhook then deliver_webhook(report)
17
+ else deliver_rails_logger(report)
18
+ end
19
+ end
20
+
21
+ def post_json(url, payload, headers: {})
22
+ require 'net/http'
23
+ uri = URI(url)
24
+ request = Net::HTTP::Post.new(uri)
25
+ request['Content-Type'] = 'application/json'
26
+ headers.each { |key, value| request[key] = value }
27
+ request.body = JSON.generate(payload)
28
+ Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
29
+ http.request(request)
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def deliver_rails_logger(report)
36
+ Rails.logger.warn("[SolidQueueGuard] status=#{report.status} warnings=#{report.warnings.join('; ')}")
37
+ end
38
+
39
+ def deliver_slack(report)
40
+ webhook_url = ENV.fetch('SOLID_QUEUE_GUARD_SLACK_WEBHOOK_URL', nil)
41
+ return if webhook_url.blank?
42
+
43
+ post_json(webhook_url, { text: "SolidQueueGuard status: *#{report.status}*\n#{report.warnings.join("\n")}" })
44
+ end
45
+
46
+ def deliver_datadog(report)
47
+ api_key = ENV.fetch('DD_API_KEY', nil)
48
+ return if api_key.blank?
49
+
50
+ alert_type = case report.status
51
+ when :unhealthy then 'error'
52
+ when :degraded then 'warning'
53
+ else 'info'
54
+ end
55
+
56
+ post_json(
57
+ 'https://api.datadoghq.com/api/v1/events',
58
+ {
59
+ title: 'SolidQueueGuard alert',
60
+ text: report.warnings.join("\n"),
61
+ alert_type: alert_type,
62
+ tags: ["status:#{report.status}"]
63
+ },
64
+ headers: { 'DD-API-KEY' => api_key }
65
+ )
66
+ end
67
+
68
+ def deliver_webhook(report)
69
+ webhook_url = ENV.fetch('SOLID_QUEUE_GUARD_WEBHOOK_URL', nil)
70
+ return if webhook_url.blank?
71
+
72
+ post_json(webhook_url, report.to_h)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -4,14 +4,9 @@ module SolidQueueGuard
4
4
  module Notifiers
5
5
  # @api private
6
6
  class Base
7
- def self.deliver(_report)
8
- raise NotImplementedError, 'Notification adapters ship in v0.3'
7
+ def self.deliver(report)
8
+ Notifier.deliver(:rails_logger, report)
9
9
  end
10
10
  end
11
-
12
- class RailsLogger < Base; end
13
- class Slack < Base; end
14
- class Datadog < Base; end
15
- class Webhook < Base; end
16
11
  end
17
12
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ # @api private
5
+ module PumaPluginSupport
6
+ module_function
7
+
8
+ PUMA_PLUGIN_PATTERN = /plugin\s+:?solid_queue/
9
+ ASYNC_MODE_PATTERN = /solid_queue_mode\s+:?async/
10
+
11
+ def puma_config_path
12
+ Rails.root.join('config/puma.rb')
13
+ end
14
+
15
+ def puma_config_content
16
+ return unless puma_config_path.exist?
17
+
18
+ puma_config_path.read
19
+ end
20
+
21
+ def puma_plugin_enabled?
22
+ content = puma_config_content
23
+ content.present? && content.match?(PUMA_PLUGIN_PATTERN)
24
+ end
25
+
26
+ def puma_async_mode?
27
+ content = puma_config_content
28
+ content.present? && content.match?(ASYNC_MODE_PATTERN)
29
+ end
30
+
31
+ def async_supervisor_mode?
32
+ ENV.fetch('SOLID_QUEUE_SUPERVISOR_MODE', 'fork').casecmp('async').zero? || puma_async_mode?
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidQueueGuard
4
+ module Recommendations
5
+ class Topology
6
+ def self.analyze(configuration: SolidQueue::Configuration.new)
7
+ new(configuration: configuration).analyze
8
+ end
9
+
10
+ def initialize(configuration:)
11
+ @configuration = configuration
12
+ end
13
+
14
+ def analyze
15
+ recommendations = []
16
+ recommendations.concat(worker_recommendations)
17
+ recommendations.concat(pool_recommendations)
18
+ recommendations.uniq
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :configuration
24
+
25
+ def worker_recommendations
26
+ worker_queues = QueueCoverage.worker_queues_from_configuration(configuration)
27
+ uncovered = uncovered_queues(worker_queues)
28
+ return [] if uncovered.empty?
29
+
30
+ ["Add worker coverage for: #{uncovered.join(', ')}"]
31
+ end
32
+
33
+ def pool_recommendations
34
+ return async_pool_recommendation if PumaPluginSupport.async_supervisor_mode?
35
+
36
+ required_threads = configuration.estimated_number_of_threads
37
+ pool_size = SolidQueue::Record.connection_pool&.size
38
+ return [] if pool_size.nil? || required_threads <= pool_size
39
+
40
+ ["Increase queue DB pool to at least #{required_threads + 2} (currently #{pool_size})"]
41
+ rescue StandardError
42
+ []
43
+ end
44
+
45
+ def uncovered_queues(assigned_queues)
46
+ observed = observed_queues
47
+ (observed - assigned_queues).sort
48
+ end
49
+
50
+ def observed_queues
51
+ return [] unless database_available?
52
+
53
+ SolidQueue::Job.distinct.pluck(:queue_name)
54
+ rescue StandardError
55
+ []
56
+ end
57
+
58
+ def database_available?
59
+ SolidQueue::Record.connection_pool&.with_connection(&:active?)
60
+ rescue StandardError
61
+ false
62
+ end
63
+
64
+ def async_pool_recommendation
65
+ required_threads = configuration.estimated_number_of_threads
66
+ pool_size = SolidQueue::Record.connection_pool&.size
67
+ return [] if pool_size.nil? || required_threads <= pool_size
68
+
69
+ ["Increase queue DB pool to at least #{required_threads + 2} " \
70
+ "for async supervisor mode (currently #{pool_size})"]
71
+ rescue StandardError
72
+ []
73
+ end
74
+ end
75
+ end
76
+ end
@@ -27,10 +27,19 @@ module SolidQueueGuard
27
27
  end
28
28
 
29
29
  def run_check(check_class)
30
+ check_id = Checks::Registry.check_id_for(check_class)
31
+ unless SolidQueueGuard.config.check_enabled?(check_id)
32
+ return Check::Result.new(
33
+ id: check_id,
34
+ status: :skip,
35
+ message: 'Check disabled via configuration'
36
+ )
37
+ end
38
+
30
39
  check_class.call
31
40
  rescue StandardError => e
32
41
  Check::Result.new(
33
- id: check_class.name.demodulize.underscore,
42
+ id: check_id,
34
43
  status: :fail,
35
44
  message: "Check raised an error: #{e.class}: #{e.message}"
36
45
  )
@@ -17,7 +17,7 @@ namespace :solid_queue_guard do
17
17
 
18
18
  desc 'Print machine-readable health status'
19
19
  task health: :environment do
20
- SolidQueueGuard::CLI.run!(scope: :config, format: :json, strict: cli_strict)
20
+ SolidQueueGuard::CLI.run!(scope: :all, format: :json, strict: cli_strict)
21
21
  end
22
22
 
23
23
  desc 'Print full diagnostic report. Usage: report[json]'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidQueueGuard
4
- VERSION = '0.1.0'
4
+ VERSION = '1.0.0'
5
5
  end
@@ -18,7 +18,7 @@ module SolidQueueGuard
18
18
  attr_accessor :configuration
19
19
 
20
20
  def deprecator
21
- @deprecator ||= ActiveSupport::Deprecation.new('0.2', 'SolidQueueGuard')
21
+ @deprecator ||= ActiveSupport::Deprecation.new('2.0', 'SolidQueueGuard')
22
22
  end
23
23
 
24
24
  def configure
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/solid_queue_guard/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'solid_queue_guard'
7
+ spec.version = SolidQueueGuard::VERSION
8
+ spec.authors = ['Rafael Pissardo']
9
+ spec.email = ['rpissardo@users.noreply.github.com']
10
+ spec.homepage = 'https://github.com/rafael-pissardo/solid_queue_guard'
11
+ spec.summary = 'Production readiness checks and runtime guards for Rails Solid Queue.'
12
+ spec.description = [
13
+ 'Detect queue lag, dead workers, unsafe thread/pool configuration,',
14
+ 'and broken recurring jobs before they become incidents.'
15
+ ].join(' ')
16
+ spec.license = 'MIT'
17
+
18
+ spec.metadata = {
19
+ 'homepage_uri' => spec.homepage,
20
+ 'source_code_uri' => 'https://github.com/rafael-pissardo/solid_queue_guard',
21
+ 'changelog_uri' => 'https://github.com/rafael-pissardo/solid_queue_guard/blob/main/CHANGELOG.md',
22
+ 'bug_tracker_uri' => 'https://github.com/rafael-pissardo/solid_queue_guard/issues',
23
+ 'documentation_uri' => 'https://github.com/rafael-pissardo/solid_queue_guard#readme',
24
+ 'rubygems_mfa_required' => 'true'
25
+ }
26
+
27
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
28
+ `git ls-files -z`.split("\x0").reject do |file|
29
+ file.start_with?('test/', '.github/', 'docs/', 'script/', 'gemfiles/', 'Appraisals')
30
+ end
31
+ end
32
+
33
+ spec.require_paths = ['lib']
34
+ spec.required_ruby_version = '>= 3.1'
35
+
36
+ rails_version = '>= 7.1', '< 9.0'
37
+
38
+ spec.add_dependency 'actionpack', rails_version
39
+ spec.add_dependency 'activejob', rails_version
40
+ spec.add_dependency 'activerecord', rails_version
41
+ spec.add_dependency 'activesupport', rails_version
42
+ spec.add_dependency 'railties', rails_version
43
+ spec.add_dependency 'solid_queue', '>= 1.0', '< 2.0'
44
+
45
+ spec.add_development_dependency 'appraisal', '~> 2.5'
46
+ spec.add_development_dependency 'debug'
47
+ spec.add_development_dependency 'minitest', '~> 5.0'
48
+ spec.add_development_dependency 'mocha', '>= 2.1'
49
+ spec.add_development_dependency 'puma', '>= 6.0'
50
+ spec.add_development_dependency 'rails', rails_version
51
+ spec.add_development_dependency 'rubocop', '~> 1.75'
52
+ spec.add_development_dependency 'rubocop-rails', '~> 2.30'
53
+ spec.add_development_dependency 'sqlite3', '~> 2.1'
54
+ end