honeybadger 5.16.0 → 5.26.4

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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +122 -0
  3. data/lib/honeybadger/agent.rb +16 -0
  4. data/lib/honeybadger/backend/test.rb +5 -1
  5. data/lib/honeybadger/backtrace.rb +2 -1
  6. data/lib/honeybadger/breadcrumbs/active_support.rb +1 -1
  7. data/lib/honeybadger/cli/test.rb +1 -0
  8. data/lib/honeybadger/config/defaults.rb +105 -3
  9. data/lib/honeybadger/config.rb +15 -1
  10. data/lib/honeybadger/gauge.rb +1 -0
  11. data/lib/honeybadger/histogram.rb +14 -0
  12. data/lib/honeybadger/init/hanami.rb +1 -1
  13. data/lib/honeybadger/init/rails.rb +9 -4
  14. data/lib/honeybadger/init/sinatra.rb +2 -2
  15. data/lib/honeybadger/instrumentation.rb +57 -21
  16. data/lib/honeybadger/instrumentation_helper.rb +17 -11
  17. data/lib/honeybadger/karafka.rb +301 -0
  18. data/lib/honeybadger/notice.rb +14 -7
  19. data/lib/honeybadger/notification_subscriber.rb +39 -19
  20. data/lib/honeybadger/plugins/active_job.rb +1 -2
  21. data/lib/honeybadger/plugins/autotuner.rb +9 -6
  22. data/lib/honeybadger/plugins/delayed_job.rb +1 -0
  23. data/lib/honeybadger/plugins/faktory.rb +1 -0
  24. data/lib/honeybadger/plugins/karafka.rb +7 -16
  25. data/lib/honeybadger/plugins/net_http.rb +17 -3
  26. data/lib/honeybadger/plugins/rails.rb +7 -1
  27. data/lib/honeybadger/plugins/resque.rb +1 -0
  28. data/lib/honeybadger/plugins/shoryuken.rb +1 -0
  29. data/lib/honeybadger/plugins/sidekiq.rb +107 -79
  30. data/lib/honeybadger/plugins/solid_queue.rb +38 -12
  31. data/lib/honeybadger/plugins/sucker_punch.rb +1 -0
  32. data/lib/honeybadger/plugins/thor.rb +1 -0
  33. data/lib/honeybadger/registry_execution.rb +1 -0
  34. data/lib/honeybadger/version.rb +1 -1
  35. data/lib/puma/plugin/honeybadger.rb +9 -3
  36. metadata +18 -6
@@ -38,10 +38,14 @@ module Honeybadger
38
38
  raise
39
39
  ensure
40
40
  context.merge!(duration: duration, status: status)
41
- Honeybadger.event('perform.sidekiq', context)
41
+ if Honeybadger.config.load_plugin_insights_events?(:sidekiq)
42
+ Honeybadger.event('perform.sidekiq', context)
43
+ end
42
44
 
43
- metric_source 'sidekiq'
44
- histogram 'perform', { bins: [30, 60, 120, 300, 1800, 3600, 21_600] }.merge(context.slice(:worker, :queue, :duration))
45
+ if Honeybadger.config.load_plugin_insights_metrics?(:sidekiq)
46
+ metric_source 'sidekiq'
47
+ gauge 'perform', context.slice(:worker, :queue, :duration)
48
+ end
45
49
  end
46
50
  end
47
51
  end
@@ -55,7 +59,9 @@ module Honeybadger
55
59
  queue: queue
56
60
  }
57
61
 
58
- Honeybadger.event('enqueue.sidekiq', context)
62
+ if Honeybadger.config.load_plugin_insights_events?(:sidekiq)
63
+ Honeybadger.event('enqueue.sidekiq', context)
64
+ end
59
65
 
60
66
  yield
61
67
  end
@@ -67,60 +73,62 @@ module Honeybadger
67
73
  requirement { defined?(::Sidekiq) }
68
74
 
69
75
  execution do
70
- ::Sidekiq.configure_server do |sidekiq|
71
- sidekiq.server_middleware do |chain|
72
- chain.prepend Middleware
76
+ if Honeybadger.config[:'exceptions.enabled']
77
+ ::Sidekiq.configure_server do |sidekiq|
78
+ sidekiq.server_middleware do |chain|
79
+ chain.prepend Middleware
80
+ end
73
81
  end
74
- end
75
82
 
76
- if defined?(::Sidekiq::VERSION) && ::Sidekiq::VERSION > '3'
77
- ::Sidekiq.configure_server do |sidekiq|
83
+ if defined?(::Sidekiq::VERSION) && ::Sidekiq::VERSION > '3'
84
+ ::Sidekiq.configure_server do |sidekiq|
78
85
 
79
- sidekiq_default_configuration = (::Sidekiq::VERSION > '7') ?
80
- ::Sidekiq.default_configuration : Class.new
86
+ sidekiq_default_configuration = (::Sidekiq::VERSION > '7') ?
87
+ ::Sidekiq.default_configuration : Class.new
81
88
 
82
- sidekiq.error_handlers << lambda { |ex, sidekiq_params, sidekiq_config = sidekiq_default_configuration|
83
- params = sidekiq_params.dup
84
- if defined?(::Sidekiq::Config)
85
- if params[:_config].is_a?(::Sidekiq::Config) # Sidekiq > 6 and < 7.1.5
86
- params[:_config] = params[:_config].instance_variable_get(:@options)
87
- else # Sidekiq >= 7.1.5
88
- params[:_config] = sidekiq_config.instance_variable_get(:@options)
89
+ sidekiq.error_handlers << lambda { |ex, sidekiq_params, sidekiq_config = sidekiq_default_configuration|
90
+ params = sidekiq_params.dup
91
+ if defined?(::Sidekiq::Config)
92
+ if params[:_config].is_a?(::Sidekiq::Config) # Sidekiq > 6 and < 7.1.5
93
+ params[:_config] = params[:_config].instance_variable_get(:@options)
94
+ else # Sidekiq >= 7.1.5
95
+ params[:_config] = sidekiq_config.instance_variable_get(:@options)
96
+ end
89
97
  end
90
- end
91
98
 
92
- job = params[:job] || params
99
+ job = params[:job] || params
93
100
 
94
- job_retry = job['retry'.freeze]
101
+ job_retry = job['retry'.freeze]
95
102
 
96
- if (threshold = config[:'sidekiq.attempt_threshold'].to_i) > 0 && job_retry
97
- # We calculate the job attempts to determine the need to
98
- # skip. Sidekiq's first job execution will have nil for the
99
- # 'retry_count' job key. The first retry will have 0 set for
100
- # the 'retry_count' key, incrementing on each execution
101
- # afterwards.
102
- retry_count = job['retry_count'.freeze]
103
- attempt = retry_count ? retry_count + 1 : 0
103
+ if (threshold = config[:'sidekiq.attempt_threshold'].to_i) > 0 && job_retry
104
+ # We calculate the job attempts to determine the need to
105
+ # skip. Sidekiq's first job execution will have nil for the
106
+ # 'retry_count' job key. The first retry will have 0 set for
107
+ # the 'retry_count' key, incrementing on each execution
108
+ # afterwards.
109
+ retry_count = job['retry_count'.freeze]
110
+ attempt = retry_count ? retry_count + 1 : 0
104
111
 
105
- max_retries = (::Sidekiq::VERSION > '7') ?
106
- ::Sidekiq.default_configuration[:max_retries] : sidekiq.options[:max_retries]
107
- # Ensure we account for modified max_retries setting
108
- default_max_retry_attempts = defined?(::Sidekiq::JobRetry::DEFAULT_MAX_RETRY_ATTEMPTS) ? ::Sidekiq::JobRetry::DEFAULT_MAX_RETRY_ATTEMPTS : 25
109
- retry_limit = job_retry == true ? (max_retries || default_max_retry_attempts) : job_retry.to_i
112
+ max_retries = (::Sidekiq::VERSION > '7') ?
113
+ ::Sidekiq.default_configuration[:max_retries] : sidekiq.options[:max_retries]
114
+ # Ensure we account for modified max_retries setting
115
+ default_max_retry_attempts = defined?(::Sidekiq::JobRetry::DEFAULT_MAX_RETRY_ATTEMPTS) ? ::Sidekiq::JobRetry::DEFAULT_MAX_RETRY_ATTEMPTS : 25
116
+ retry_limit = job_retry == true ? (max_retries || default_max_retry_attempts) : job_retry.to_i
110
117
 
111
- limit = [retry_limit, threshold].min
118
+ limit = [retry_limit, threshold].min
112
119
 
113
- return if attempt < limit
114
- end
120
+ return if attempt < limit
121
+ end
115
122
 
116
- opts = { parameters: params }
117
- if config[:'sidekiq.use_component']
118
- opts[:component] = job['wrapped'.freeze] || job['class'.freeze]
119
- opts[:action] = 'perform' if opts[:component]
120
- end
123
+ opts = { parameters: params }
124
+ if config[:'sidekiq.use_component']
125
+ opts[:component] = job['wrapped'.freeze] || job['class'.freeze]
126
+ opts[:action] = 'perform' if opts[:component]
127
+ end
121
128
 
122
- Honeybadger.notify(ex, opts)
123
- }
129
+ Honeybadger.notify(ex, opts)
130
+ }
131
+ end
124
132
  end
125
133
  end
126
134
 
@@ -161,47 +169,67 @@ module Honeybadger
161
169
  end
162
170
  end
163
171
 
172
+ collect_sidekiq_stats = -> do
173
+ stats = ::Sidekiq::Stats.new
174
+ data = stats.as_json
175
+ data[:queues] = {}
176
+
177
+ ::Sidekiq::Queue.all.each do |queue|
178
+ data[:queues][queue.name] ||= {}
179
+ data[:queues][queue.name][:latency] = (queue.latency * 1000).ceil
180
+ data[:queues][queue.name][:depth] = queue.size
181
+ end
182
+
183
+ Hash.new(0).tap do |busy_counts|
184
+ ::Sidekiq::Workers.new.each do |_pid, _tid, work|
185
+ payload = work.respond_to?(:payload) ? work.payload : work["payload"]
186
+ payload = JSON.parse(payload) if payload.is_a?(String)
187
+ busy_counts[payload["queue"]] += 1
188
+ end
189
+ end.each do |queue_name, busy_count|
190
+ data[:queues][queue_name] ||= {}
191
+ data[:queues][queue_name][:busy] = busy_count
192
+ end
193
+
194
+ processes = ::Sidekiq::ProcessSet.new.to_enum(:each).to_a
195
+ data[:capacity] = processes.map { |process| process["concurrency"] }.sum
196
+
197
+ process_utilizations = processes.map do |process|
198
+ next unless process["concurrency"].to_f > 0
199
+ process["busy"] / process["concurrency"].to_f
200
+ end.compact
201
+
202
+ if process_utilizations.any?
203
+ utilization = process_utilizations.sum / process_utilizations.length.to_f
204
+ data[:utilization] = utilization
205
+ end
206
+
207
+ data
208
+ end
209
+
164
210
  collect do
165
211
  if config.cluster_collection?(:sidekiq) && (leader_checker.nil? || leader_checker.collect?)
166
- metric_source 'sidekiq'
167
-
168
- stats = ::Sidekiq::Stats.new
169
-
170
- gauge 'active_workers', ->{ stats.workers_size }
171
- gauge 'active_processes', ->{ stats.processes_size }
172
- gauge 'jobs_processed', ->{ stats.processed }
173
- gauge 'jobs_failed', ->{ stats.failed }
174
- gauge 'jobs_scheduled', ->{ stats.scheduled_size }
175
- gauge 'jobs_enqueued', ->{ stats.enqueued }
176
- gauge 'jobs_dead', ->{ stats.dead_size }
177
- gauge 'jobs_retry', ->{ stats.retry_size }
178
-
179
- ::Sidekiq::Queue.all.each do |queue|
180
- gauge 'queue_latency', { queue: queue.name }, ->{ (queue.latency * 1000).ceil }
181
- gauge 'queue_depth', { queue: queue.name }, ->{ queue.size }
182
- end
212
+ stats = collect_sidekiq_stats.call
183
213
 
184
- Hash.new(0).tap do |busy_counts|
185
- ::Sidekiq::Workers.new.each do |_pid, _tid, work|
186
- payload = work.respond_to?(:payload) ? work.payload : work["payload"]
187
- payload = JSON.parse(payload) if payload.is_a?(String)
188
- busy_counts[payload["queue"]] += 1
189
- end
190
- end.each do |queue_name, busy_count|
191
- gauge 'queue_busy', { queue: queue_name }, ->{ busy_count }
214
+ if Honeybadger.config.load_plugin_insights_events?(:sidekiq)
215
+ Honeybadger.event('stats.sidekiq', stats.except('stats').merge(stats['stats']))
192
216
  end
193
217
 
194
- processes = ::Sidekiq::ProcessSet.new.to_enum(:each).to_a
195
- gauge 'capacity', ->{ processes.map { |process| process["concurrency"] }.sum }
218
+ if Honeybadger.config.load_plugin_insights_metrics?(:sidekiq)
219
+ metric_source 'sidekiq'
196
220
 
197
- process_utilizations = processes.map do |process|
198
- next unless process["concurrency"].to_f > 0
199
- process["busy"] / process["concurrency"].to_f
200
- end.compact
221
+ stats['stats'].each do |name, value|
222
+ gauge name, value: value
223
+ end
224
+
225
+ stats[:queues].each do |queue_name, data|
226
+ data.each do |key, value|
227
+ gauge "queue_#{key}", queue: queue_name, value: value
228
+ end
229
+ end
201
230
 
202
- if process_utilizations.any?
203
- utilization = process_utilizations.sum / process_utilizations.length.to_f
204
- gauge 'utilization', ->{ utilization }
231
+ gauge 'capacity', value: stats[:capacity] if stats[:capacity]
232
+ gauge 'utilization', value: stats[:utilization] if stats[:utilization]
205
233
  end
206
234
  end
207
235
  end
@@ -4,20 +4,46 @@ module Honeybadger
4
4
  Plugin.register :solid_queue do
5
5
  requirement { config.load_plugin_insights?(:solid_queue) && defined?(::SolidQueue) }
6
6
 
7
+ collect_solid_queue_stats = -> do
8
+ data = {}
9
+ data[:stats] = {
10
+ jobs_in_progress: ::SolidQueue::ClaimedExecution.count,
11
+ jobs_blocked: ::SolidQueue::BlockedExecution.count,
12
+ jobs_failed: ::SolidQueue::FailedExecution.count,
13
+ jobs_scheduled: ::SolidQueue::ScheduledExecution.count,
14
+ jobs_processed: ::SolidQueue::Job.where.not(finished_at: nil).count,
15
+ active_workers: ::SolidQueue::Process.where(kind: "Worker").count,
16
+ active_dispatchers: ::SolidQueue::Process.where(kind: "Dispatcher").count
17
+ }
18
+
19
+ data[:queues] = {}
20
+
21
+ ::SolidQueue::Queue.all.each do |queue|
22
+ data[:queues][queue.name] = { depth: queue.size }
23
+ end
24
+
25
+ data
26
+ end
27
+
7
28
  collect do
29
+ stats = collect_solid_queue_stats.call
30
+
8
31
  if config.cluster_collection?(:solid_queue)
9
- metric_source 'solid_queue'
10
-
11
- gauge 'jobs_in_progress', ->{ ::SolidQueue::ClaimedExecution.count }
12
- gauge 'jobs_blocked', ->{ ::SolidQueue::BlockedExecution.count }
13
- gauge 'jobs_failed', ->{ ::SolidQueue::FailedExecution.count }
14
- gauge 'jobs_scheduled', ->{ ::SolidQueue::ScheduledExecution.count }
15
- gauge 'jobs_processed', ->{ ::SolidQueue::Job.where.not(finished_at: nil).count }
16
- gauge 'active_workers', ->{ ::SolidQueue::Process.where(kind: "Worker").count }
17
- gauge 'active_dispatchers', ->{ ::SolidQueue::Process.where(kind: "Dispatcher").count }
18
-
19
- ::SolidQueue::Queue.all.each do |queue|
20
- gauge 'queue_depth', { queue: queue.name }, ->{ queue.size }
32
+ if Honeybadger.config.load_plugin_insights_events?(:solid_queue)
33
+ Honeybadger.event('stats.solid_queue', stats.except(:stats).merge(stats[:stats]))
34
+ end
35
+
36
+ if Honeybadger.config.load_plugin_insights_metrics?(:solid_queue)
37
+ metric_source 'solid_queue'
38
+ stats[:stats].each do |stat_name, value|
39
+ gauge stat_name, value: value
40
+ end
41
+
42
+ stats[:queues].each do |queue_name, data|
43
+ data.each do |key, value|
44
+ gauge "queue_#{key}", queue: queue_name, value: value
45
+ end
46
+ end
21
47
  end
22
48
  end
23
49
  end
@@ -6,6 +6,7 @@ module Honeybadger
6
6
  requirement { defined?(::SuckerPunch) }
7
7
 
8
8
  execution do
9
+ return unless Honeybadger.config[:'exceptions.enabled']
9
10
  if SuckerPunch.respond_to?(:exception_handler=) # >= v2
10
11
  SuckerPunch.exception_handler = ->(ex, klass, args) { Honeybadger.notify(ex, { :component => klass, :parameters => args }) }
11
12
  else
@@ -25,6 +25,7 @@ module Honeybadger
25
25
  requirement { defined?(::Thor.no_commands) }
26
26
 
27
27
  execution do
28
+ return unless Honeybadger.config[:'exceptions.enabled']
28
29
  ::Thor.send(:include, Thor)
29
30
  end
30
31
  end
@@ -19,6 +19,7 @@ module Honeybadger
19
19
 
20
20
  def call
21
21
  @registry.metrics.each do |metric|
22
+ next if metric.samples == 0
22
23
  metric.event_payloads.each do |payload|
23
24
  Honeybadger.event(payload.merge(interval: @interval))
24
25
  end
@@ -1,4 +1,4 @@
1
1
  module Honeybadger
2
2
  # The current String Honeybadger version.
3
- VERSION = '5.16.0'.freeze
3
+ VERSION = '5.26.4'.freeze
4
4
  end
@@ -12,7 +12,7 @@ module Honeybadger
12
12
  in_background do
13
13
  loop do
14
14
  puma_plugin.record
15
- sleep 1
15
+ sleep [::Honeybadger.config.collection_interval(:puma).to_i, 1].max
16
16
  end
17
17
  end
18
18
  end
@@ -35,8 +35,14 @@ module Honeybadger
35
35
  end
36
36
 
37
37
  def record_puma_stats(stats, context={})
38
- STATS_KEYS.each do |stat|
39
- gauge stat, context, ->{ stats[stat] } if stats[stat]
38
+ if Honeybadger.config.load_plugin_insights_events?(:puma)
39
+ Honeybadger.event('stats.puma', context.merge(stats))
40
+ end
41
+
42
+ if Honeybadger.config.load_plugin_insights_metrics?(:puma)
43
+ STATS_KEYS.each do |stat|
44
+ gauge stat, context, ->{ stats[stat] } if stats[stat]
45
+ end
40
46
  end
41
47
  end
42
48
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: honeybadger
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.16.0
4
+ version: 5.26.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Honeybadger Industries LLC
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-09-19 00:00:00.000000000 Z
10
+ date: 2025-02-18 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: logger
@@ -24,6 +23,20 @@ dependencies:
24
23
  - - ">="
25
24
  - !ruby/object:Gem::Version
26
25
  version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: ostruct
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
27
40
  description: Make managing application errors a more pleasant experience.
28
41
  email:
29
42
  - support@honeybadger.io
@@ -81,6 +94,7 @@ files:
81
94
  - lib/honeybadger/init/sinatra.rb
82
95
  - lib/honeybadger/instrumentation.rb
83
96
  - lib/honeybadger/instrumentation_helper.rb
97
+ - lib/honeybadger/karafka.rb
84
98
  - lib/honeybadger/logging.rb
85
99
  - lib/honeybadger/metric.rb
86
100
  - lib/honeybadger/metrics_worker.rb
@@ -175,7 +189,6 @@ metadata:
175
189
  documentation_uri: https://docs.honeybadger.io/lib/ruby/
176
190
  homepage_uri: https://www.honeybadger.io/for/ruby/
177
191
  source_code_uri: https://github.com/honeybadger-io/honeybadger-ruby
178
- post_install_message:
179
192
  rdoc_options:
180
193
  - "--markup=tomdoc"
181
194
  - "--main=README.md"
@@ -193,8 +206,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
193
206
  - !ruby/object:Gem::Version
194
207
  version: '0'
195
208
  requirements: []
196
- rubygems_version: 3.5.3
197
- signing_key:
209
+ rubygems_version: 3.7.0.dev
198
210
  specification_version: 4
199
211
  summary: Error reports you can be happy about.
200
212
  test_files: []