solid_observer 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 (34) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +58 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +347 -0
  5. data/app/jobs/solid_observer/cleanup_job.rb +12 -0
  6. data/app/models/solid_observer/queue_event.rb +23 -0
  7. data/app/models/solid_observer/queue_metric.rb +14 -0
  8. data/app/models/solid_observer/storage_info.rb +36 -0
  9. data/bin/console +11 -0
  10. data/bin/setup +8 -0
  11. data/db/migrate/20260115000001_create_solid_observer_queue_events.rb +21 -0
  12. data/db/migrate/20260115000002_create_solid_observer_metrics.rb +16 -0
  13. data/db/migrate/20260115000003_create_solid_observer_storage_info.rb +13 -0
  14. data/lib/generators/solid_observer/install_generator.rb +72 -0
  15. data/lib/generators/solid_observer/templates/initializer.rb.tt +57 -0
  16. data/lib/solid_observer/base_event.rb +10 -0
  17. data/lib/solid_observer/base_metric.rb +59 -0
  18. data/lib/solid_observer/cli/base.rb +98 -0
  19. data/lib/solid_observer/cli/jobs.rb +195 -0
  20. data/lib/solid_observer/cli/status.rb +59 -0
  21. data/lib/solid_observer/cli/storage.rb +114 -0
  22. data/lib/solid_observer/configuration.rb +125 -0
  23. data/lib/solid_observer/correlation_id_resolver.rb +62 -0
  24. data/lib/solid_observer/engine.rb +60 -0
  25. data/lib/solid_observer/queue_event_buffer.rb +80 -0
  26. data/lib/solid_observer/queue_stats.rb +89 -0
  27. data/lib/solid_observer/services/cleanup_storage.rb +94 -0
  28. data/lib/solid_observer/services/flush_event_buffer.rb +65 -0
  29. data/lib/solid_observer/services/record_event.rb +96 -0
  30. data/lib/solid_observer/subscriber.rb +96 -0
  31. data/lib/solid_observer/version.rb +7 -0
  32. data/lib/solid_observer.rb +40 -0
  33. data/lib/tasks/solid_observer.rake +155 -0
  34. metadata +93 -0
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidObserver
4
+ # Subscribes to ActiveSupport::Notifications for ActiveJob events.
5
+ #
6
+ # Monitors job lifecycle events (enqueue, perform, retry_stopped, discard)
7
+ # and records them through the event buffer for observability.
8
+ #
9
+ # @example Subscribe to job events
10
+ # SolidObserver::Subscriber.subscribe!
11
+ class Subscriber
12
+ EVENTS = %w[
13
+ enqueue.active_job
14
+ perform.active_job
15
+ retry_stopped.active_job
16
+ discard.active_job
17
+ ].freeze
18
+
19
+ class << self
20
+ def subscribe!
21
+ return unless SolidObserver.config.observe_queue
22
+ return if subscribed?
23
+
24
+ @subscriptions = []
25
+ @subscriptions << subscribe_to_enqueue
26
+ @subscriptions << subscribe_to_perform
27
+ @subscriptions << subscribe_to_retry_stopped
28
+ @subscriptions << subscribe_to_discard
29
+ @subscriptions.compact!
30
+ end
31
+
32
+ def unsubscribe!
33
+ return unless @subscriptions
34
+
35
+ @subscriptions.each do |subscription|
36
+ ActiveSupport::Notifications.unsubscribe(subscription)
37
+ end
38
+ @subscriptions = []
39
+ end
40
+
41
+ def subscribed?
42
+ !!@subscriptions&.any?
43
+ end
44
+
45
+ private
46
+
47
+ def subscribe_to_enqueue
48
+ ActiveSupport::Notifications.subscribe("enqueue.active_job") do |*args|
49
+ event = ActiveSupport::Notifications::Event.new(*args)
50
+ Services::RecordEvent.call(
51
+ event: event,
52
+ event_type: "job_enqueued",
53
+ buffer: QueueEventBuffer.instance,
54
+ metric_name: "jobs_enqueued"
55
+ )
56
+ end
57
+ end
58
+
59
+ def subscribe_to_perform
60
+ ActiveSupport::Notifications.subscribe("perform.active_job") do |*args|
61
+ event = ActiveSupport::Notifications::Event.new(*args)
62
+ Services::RecordEvent.call(
63
+ event: event,
64
+ event_type: "job_completed",
65
+ buffer: QueueEventBuffer.instance,
66
+ metric_name: "jobs_completed"
67
+ )
68
+ end
69
+ end
70
+
71
+ def subscribe_to_retry_stopped
72
+ ActiveSupport::Notifications.subscribe("retry_stopped.active_job") do |*args|
73
+ event = ActiveSupport::Notifications::Event.new(*args)
74
+ Services::RecordEvent.call(
75
+ event: event,
76
+ event_type: "job_failed",
77
+ buffer: QueueEventBuffer.instance,
78
+ metric_name: "jobs_failed"
79
+ )
80
+ end
81
+ end
82
+
83
+ def subscribe_to_discard
84
+ ActiveSupport::Notifications.subscribe("discard.active_job") do |*args|
85
+ event = ActiveSupport::Notifications::Event.new(*args)
86
+ Services::RecordEvent.call(
87
+ event: event,
88
+ event_type: "job_discarded",
89
+ buffer: QueueEventBuffer.instance,
90
+ metric_name: "jobs_discarded"
91
+ )
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidObserver
4
+ VERSION = "0.1.0"
5
+ RUBY_MINIMUM_VERSION = "3.2.0"
6
+ RAILS_MINIMUM_VERSION = "8.0"
7
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "solid_observer/version"
4
+ require_relative "solid_observer/configuration"
5
+ require_relative "solid_observer/correlation_id_resolver"
6
+ require_relative "solid_observer/base_event" if defined?(ActiveRecord)
7
+ require_relative "solid_observer/base_metric" if defined?(ActiveRecord)
8
+ require_relative "solid_observer/services/record_event" if defined?(ActiveRecord)
9
+ require_relative "solid_observer/services/flush_event_buffer" if defined?(ActiveRecord)
10
+ require_relative "solid_observer/services/cleanup_storage" if defined?(ActiveRecord)
11
+ require_relative "solid_observer/queue_event_buffer" if defined?(ActiveRecord)
12
+ require_relative "solid_observer/subscriber" if defined?(ActiveSupport)
13
+ require_relative "solid_observer/cli/base"
14
+ require_relative "solid_observer/cli/status"
15
+ require_relative "solid_observer/cli/storage"
16
+ require_relative "solid_observer/cli/jobs"
17
+ require_relative "solid_observer/queue_stats"
18
+ require_relative "solid_observer/engine" if defined?(Rails::Engine)
19
+
20
+ module SolidObserver
21
+ class Error < StandardError; end
22
+
23
+ class << self
24
+ def configuration
25
+ @configuration ||= Configuration.new
26
+ end
27
+
28
+ def configure
29
+ yield(configuration)
30
+ end
31
+
32
+ def config
33
+ configuration
34
+ end
35
+
36
+ def reset_configuration!
37
+ @configuration = Configuration.new
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :solid_observer do
4
+ desc "Display SolidObserver version"
5
+ task :version do
6
+ puts "SolidObserver #{SolidObserver::VERSION}"
7
+ puts "Ruby: #{SolidObserver::RUBY_MINIMUM_VERSION}+"
8
+ puts "Rails: #{SolidObserver::RAILS_MINIMUM_VERSION}+"
9
+ end
10
+
11
+ namespace :install do
12
+ desc "Copy SolidObserver migrations to your application"
13
+ task migrations: :environment do
14
+ Rake::Task["railties:install:migrations"].reenable
15
+ Rake::Task["railties:install:migrations"].invoke
16
+ end
17
+ end
18
+
19
+ namespace :db do
20
+ desc "Create the SolidObserver database"
21
+ task create: :environment do
22
+ config = ActiveRecord::Base.configurations.configs_for(env_name: Rails.env, name: "solid_observer_queue")
23
+ ActiveRecord::Tasks::DatabaseTasks.create(config)
24
+ puts "Created database '#{config.database}'"
25
+ end
26
+
27
+ desc "Migrate the SolidObserver database"
28
+ task migrate: :environment do
29
+ ActiveRecord::Tasks::DatabaseTasks.migrate
30
+ puts "Migrations complete"
31
+ end
32
+ end
33
+
34
+ namespace :buffer do
35
+ desc "Flush the event buffer to the database"
36
+ task flush: :environment do
37
+ buffer = SolidObserver::QueueEventBuffer.instance
38
+ buffer_size = buffer.size
39
+
40
+ if buffer_size.zero?
41
+ puts "Buffer is empty, nothing to flush"
42
+ else
43
+ puts "Flushing #{buffer_size} events from buffer..."
44
+ buffer.flush!
45
+ puts "✓ Buffer flushed successfully"
46
+ end
47
+ end
48
+
49
+ desc "Clear the event buffer without flushing to database"
50
+ task clear: :environment do
51
+ buffer = SolidObserver::QueueEventBuffer.instance
52
+ buffer_size = buffer.size
53
+
54
+ if buffer_size.zero?
55
+ puts "Buffer is already empty"
56
+ else
57
+ buffer.clear
58
+ puts "✓ Cleared #{buffer_size} events from buffer (not saved to database)"
59
+ end
60
+ end
61
+ end
62
+
63
+ namespace :storage do
64
+ desc "Run storage cleanup based on retention policy"
65
+ task cleanup: :environment do
66
+ retention = SolidObserver.config.event_retention
67
+ puts "Running storage cleanup (retention: #{retention.inspect})..."
68
+
69
+ deleted_count = SolidObserver::Services::CleanupStorage.call
70
+ puts "✓ Cleanup complete: #{deleted_count} old events deleted"
71
+ end
72
+
73
+ desc "Purge ALL SolidObserver storage data (use with caution!)"
74
+ task purge: :environment do
75
+ print "⚠️ This will delete ALL SolidObserver data (events + snapshots). Are you sure? (y/N) "
76
+ $stdout.flush
77
+
78
+ confirmation = $stdin.gets&.strip&.downcase
79
+ if confirmation == "y"
80
+ event_count = SolidObserver::QueueEvent.count
81
+ snapshot_count = SolidObserver::StorageInfo.count
82
+
83
+ SolidObserver::QueueEvent.delete_all
84
+ SolidObserver::StorageInfo.delete_all
85
+
86
+ connection = SolidObserver::QueueEvent.connection
87
+ case connection.adapter_name.downcase
88
+ when "sqlite"
89
+ connection.execute("VACUUM")
90
+ puts "✓ Database vacuumed to reclaim disk space"
91
+ when "postgresql"
92
+ connection.execute("ANALYZE solid_observer_queue_events")
93
+ connection.execute("ANALYZE solid_observer_storage_info")
94
+ puts "✓ Database tables analyzed"
95
+ end
96
+
97
+ puts "✓ Purged #{event_count} events and #{snapshot_count} storage snapshots"
98
+ else
99
+ puts "Aborted"
100
+ end
101
+ end
102
+ end
103
+
104
+ desc "Display SolidObserver status with queue statistics"
105
+ task status: :environment do
106
+ SolidObserver::CLI::Status.call
107
+ end
108
+
109
+ desc "Display storage information and database statistics"
110
+ task storage: :environment do
111
+ SolidObserver::CLI::Storage.call
112
+ end
113
+
114
+ namespace :jobs do
115
+ desc "List jobs with optional filters (status, queue, class, limit)"
116
+ task :list, [:status, :queue, :job_class, :limit] => :environment do |_t, args|
117
+ SolidObserver::CLI::Jobs.new.list(
118
+ status: args[:status],
119
+ queue: args[:queue],
120
+ job_class: args[:job_class],
121
+ limit: args[:limit] || 20
122
+ )
123
+ end
124
+
125
+ desc "Show details for a specific job by ID"
126
+ task :show, [:job_id] => :environment do |_t, args|
127
+ if args[:job_id].nil?
128
+ puts "Error: Job ID required. Usage: rails solid_observer:jobs:show[JOB_ID]"
129
+ exit 1
130
+ end
131
+
132
+ SolidObserver::CLI::Jobs.new.show(args[:job_id])
133
+ end
134
+
135
+ desc "Retry a failed job by ID"
136
+ task :retry, [:job_id] => :environment do |_t, args|
137
+ if args[:job_id].nil?
138
+ puts "Error: Job ID required. Usage: rails solid_observer:jobs:retry[JOB_ID]"
139
+ exit 1
140
+ end
141
+
142
+ SolidObserver::CLI::Jobs.new.retry_job(args[:job_id])
143
+ end
144
+
145
+ desc "Discard a failed job by ID"
146
+ task :discard, [:job_id] => :environment do |_t, args|
147
+ if args[:job_id].nil?
148
+ puts "Error: Job ID required. Usage: rails solid_observer:jobs:discard[JOB_ID]"
149
+ exit 1
150
+ end
151
+
152
+ SolidObserver::CLI::Jobs.new.discard(args[:job_id])
153
+ end
154
+ end
155
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: solid_observer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - BartOz
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rails
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '8.0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '8.0'
26
+ description: Production-grade observability for Rails 8's Solid Stack. Monitor ActiveJob
27
+ performance, track queue metrics, and debug issues with zero external dependencies.
28
+ Built-in CLI, retention policies, and APM integrations.
29
+ email:
30
+ - bartek.ozdoba@gmail.com
31
+ executables: []
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - CHANGELOG.md
36
+ - LICENSE.txt
37
+ - README.md
38
+ - app/jobs/solid_observer/cleanup_job.rb
39
+ - app/models/solid_observer/queue_event.rb
40
+ - app/models/solid_observer/queue_metric.rb
41
+ - app/models/solid_observer/storage_info.rb
42
+ - bin/console
43
+ - bin/setup
44
+ - db/migrate/20260115000001_create_solid_observer_queue_events.rb
45
+ - db/migrate/20260115000002_create_solid_observer_metrics.rb
46
+ - db/migrate/20260115000003_create_solid_observer_storage_info.rb
47
+ - lib/generators/solid_observer/install_generator.rb
48
+ - lib/generators/solid_observer/templates/initializer.rb.tt
49
+ - lib/solid_observer.rb
50
+ - lib/solid_observer/base_event.rb
51
+ - lib/solid_observer/base_metric.rb
52
+ - lib/solid_observer/cli/base.rb
53
+ - lib/solid_observer/cli/jobs.rb
54
+ - lib/solid_observer/cli/status.rb
55
+ - lib/solid_observer/cli/storage.rb
56
+ - lib/solid_observer/configuration.rb
57
+ - lib/solid_observer/correlation_id_resolver.rb
58
+ - lib/solid_observer/engine.rb
59
+ - lib/solid_observer/queue_event_buffer.rb
60
+ - lib/solid_observer/queue_stats.rb
61
+ - lib/solid_observer/services/cleanup_storage.rb
62
+ - lib/solid_observer/services/flush_event_buffer.rb
63
+ - lib/solid_observer/services/record_event.rb
64
+ - lib/solid_observer/subscriber.rb
65
+ - lib/solid_observer/version.rb
66
+ - lib/tasks/solid_observer.rake
67
+ homepage: https://solid.observer
68
+ licenses:
69
+ - MIT
70
+ metadata:
71
+ allowed_push_host: https://rubygems.org
72
+ homepage_uri: https://solid.observer
73
+ source_code_uri: https://github.com/bart-oz/solid_observer
74
+ changelog_uri: https://github.com/bart-oz/solid_observer/blob/main/CHANGELOG.md
75
+ rubygems_mfa_required: 'true'
76
+ rdoc_options: []
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: 3.2.0
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ requirements: []
90
+ rubygems_version: 4.0.3
91
+ specification_version: 4
92
+ summary: Observability for the Rails 8's Solid Stack
93
+ test_files: []