honeybadger 4.4.2 → 4.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fac77f5cc8573711baf81c791a3f7377ef05e931c36a05be1e7b00225f1f4cbd
4
- data.tar.gz: 8c622aa94f759c54d074c23dd7836c285449abb0399bd34642e616b352096f36
3
+ metadata.gz: 7cfde073c6afe9ef4757d7aaa4d86913ffb178a8d65907f0c872f1a55467c8bb
4
+ data.tar.gz: 66b637f1a022ddb27fe6ed8b64af4781d2a90bb7cdb644df349885614e56184b
5
5
  SHA512:
6
- metadata.gz: 6c1a4792a723264e8f633f41d62ba85eeef2a895fe5dd14eccd3f9bce9820b0565338618a0057508295a9e0c4661b5fb8da25a45e54f2b09c11ed4a9fb1fbdf7
7
- data.tar.gz: 872d754f468b46308632c668b8fdaa4b06786f517d16dd15f503aa05a7a814351f6c3f1c8e15b304441afd1a8cf01e1bbdbd7002ae8fd9f8abce96e4c14f7aab
6
+ metadata.gz: e4414164bbb59e6d82ff0d4ce1be1e9630f3c3fe8a35ca419740e899d0623d3375e9ef681fb402e4d5286fa01b9aac55e3190ad85f34467c9323404a6ea8fb4a
7
+ data.tar.gz: 3e8ffe0c2f9a877ca9abdf910ca556e6f9e11e6e865482807e6f03581e1c8ffc6a811c4629e91e613e037060014b589970a40910eb9758418ec2bcfe041e2e4b
@@ -5,6 +5,22 @@ adheres to [Semantic Versioning](http://semver.org/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [4.5.0] - 2019-08-05
9
+ ### Changed
10
+ - Default `max_queue_size` has been reduced from 1000 to 100.
11
+
12
+ ### Added
13
+ - Added `Notice#causes`, which allows cause data to be mutated in
14
+ `before_notify` callbacks (useful for filtering purposes).
15
+ - Added `Notice#cause=`, which allows the cause to be changed or disabled
16
+ in `before_notify` callbacks.
17
+ - Added extra shutdown logging.
18
+
19
+ ### Fixed
20
+ - `Honeybadger.notify(exception, cause: nil)` will now prevent the cause from
21
+ being reported.
22
+ - When throttled, queued notices will be discarded during shutdown.
23
+
8
24
  ## [4.4.2] - 2019-08-01
9
25
  ### Fixed
10
26
  - Handle ActiveSupport::Notifications passing nil started or finished time
@@ -323,7 +323,7 @@ module Honeybadger
323
323
  # @example
324
324
  # Honeybadger.stop # => nil
325
325
  def stop(force = false)
326
- worker.send(force ? :shutdown! : :shutdown)
326
+ worker.shutdown(force)
327
327
  true
328
328
  end
329
329
 
@@ -81,7 +81,7 @@ module Honeybadger
81
81
  },
82
82
  max_queue_size: {
83
83
  description: 'Maximum number of items for each worker queue.',
84
- default: 1000,
84
+ default: 100,
85
85
  type: Integer
86
86
  },
87
87
  plugins: {
@@ -55,6 +55,17 @@ module Honeybadger
55
55
  # The Regexp used to strip invalid characters from individual tags.
56
56
  TAG_SANITIZER = /[^\w]/.freeze
57
57
 
58
+ # @api private
59
+ class Cause
60
+ attr_accessor :error_class, :error_message, :backtrace
61
+
62
+ def initialize(cause)
63
+ self.error_class = cause.class.name
64
+ self.error_message = cause.message
65
+ self.backtrace = cause.backtrace
66
+ end
67
+ end
68
+
58
69
  # The unique ID of this notice which can be used to reference the error in
59
70
  # Honeybadger.
60
71
  attr_reader :id
@@ -64,6 +75,13 @@ module Honeybadger
64
75
 
65
76
  # The exception cause if available.
66
77
  attr_reader :cause
78
+ def cause=(cause)
79
+ @cause = cause
80
+ @causes = unwrap_causes(cause)
81
+ end
82
+
83
+ # @return [Cause] A list of exception causes (see {Cause})
84
+ attr_reader :causes
67
85
 
68
86
  # The backtrace from the given exception or hash.
69
87
  attr_accessor :backtrace
@@ -163,15 +181,13 @@ module Honeybadger
163
181
  @request_sanitizer = Util::Sanitizer.new(filters: params_filters)
164
182
 
165
183
  @exception = unwrap_exception(opts[:exception])
166
- @cause = opts[:cause] || exception_cause(@exception) || $!
167
-
168
- @causes = unwrap_causes(@cause)
169
184
 
170
185
  self.error_class = exception_attribute(:error_class, 'Notice') {|exception| exception.class.name }
171
186
  self.error_message = exception_attribute(:error_message, 'No message provided') do |exception|
172
187
  "#{exception.class.name}: #{exception.message}"
173
188
  end
174
189
  self.backtrace = exception_attribute(:backtrace, caller)
190
+ self.cause = opts.key?(:cause) ? opts[:cause] : (exception_cause(@exception) || $!)
175
191
 
176
192
  self.context = construct_context_hash(opts, exception)
177
193
  self.local_variables = local_variables_from_exception(exception, config)
@@ -213,7 +229,7 @@ module Honeybadger
213
229
  backtrace: s(parse_backtrace(backtrace)),
214
230
  fingerprint: fingerprint_hash,
215
231
  tags: s(tags),
216
- causes: s(causes)
232
+ causes: s(prepare_causes(causes))
217
233
  },
218
234
  request: request,
219
235
  server: {
@@ -256,8 +272,8 @@ module Honeybadger
256
272
 
257
273
  private
258
274
 
259
- attr_reader :config, :opts, :stats, :now, :pid, :causes,
260
- :request_sanitizer, :rack_env
275
+ attr_reader :config, :opts, :stats, :now, :pid, :request_sanitizer,
276
+ :rack_env
261
277
 
262
278
  def ignore_by_origin?
263
279
  return false if opts[:origin] != :rake
@@ -492,16 +508,12 @@ module Honeybadger
492
508
  #
493
509
  # cause - The first cause to unwrap.
494
510
  #
495
- # Returns Array causes (in Hash payload format).
511
+ # Returns the Array of Cause instances.
496
512
  def unwrap_causes(cause)
497
513
  causes, c, i = [], cause, 0
498
514
 
499
515
  while c && i < MAX_EXCEPTION_CAUSES
500
- causes << {
501
- class: c.class.name,
502
- message: c.message,
503
- backtrace: parse_backtrace(c.backtrace || caller)
504
- }
516
+ causes << Cause.new(c)
505
517
  i += 1
506
518
  c = exception_cause(c)
507
519
  end
@@ -509,6 +521,21 @@ module Honeybadger
509
521
  causes
510
522
  end
511
523
 
524
+ # Convert list of causes into payload format.
525
+ #
526
+ # causes - Array of Cause instances.
527
+ #
528
+ # Returns the Array of causes in Hash payload format.
529
+ def prepare_causes(causes)
530
+ causes.map {|c|
531
+ {
532
+ class: c.error_class,
533
+ message: c.error_message,
534
+ backtrace: parse_backtrace(c.backtrace)
535
+ }
536
+ }
537
+ end
538
+
512
539
  def params_filters
513
540
  config.params_filters + rails_params_filters
514
541
  end
@@ -1,4 +1,4 @@
1
1
  module Honeybadger
2
2
  # The current String Honeybadger version.
3
- VERSION = '4.4.2'.freeze
3
+ VERSION = '4.5.0'.freeze
4
4
  end
@@ -14,37 +14,34 @@ module Honeybadger
14
14
  # Sub-class thread so we have a named thread (useful for debugging in Thread.list).
15
15
  class Thread < ::Thread; end
16
16
 
17
- # A queue which enforces a maximum size.
18
- class Queue < ::Queue
19
- attr_reader :max_size
20
-
21
- def initialize(max_size)
22
- @mutex = Mutex.new
23
- @max_size = max_size
24
- super()
25
- end
26
-
27
- def push(msg)
28
- @mutex.synchronize do
29
- super unless size >= max_size
30
- end
31
- end
32
- end
33
-
17
+ # Used to signal the worker to shutdown.
34
18
  SHUTDOWN = :__hb_worker_shutdown!
35
19
 
20
+ # The base number for the exponential backoff formula when calculating the
21
+ # throttle interval. `1.05 ** throttle` will reach an interval of 2 minutes
22
+ # after around 100 429 responses from the server.
23
+ BASE_THROTTLE = 1.05
24
+
36
25
  def initialize(config)
37
26
  @config = config
38
- @throttles = []
27
+ @throttle = 0
28
+ @throttle_interval = 0
39
29
  @mutex = Mutex.new
40
30
  @marker = ConditionVariable.new
41
- @queue = Queue.new(config.max_queue_size)
31
+ @queue = Queue.new
42
32
  @shutdown = false
43
33
  @start_at = nil
34
+ @pid = Process.pid
44
35
  end
45
36
 
46
37
  def push(msg)
47
38
  return false unless start
39
+
40
+ if queue.size >= config.max_queue_size
41
+ warn { sprintf('Unable to report error; reached max queue size of %s. id=%s', queue.size, msg.id) }
42
+ return false
43
+ end
44
+
48
45
  queue.push(msg)
49
46
  end
50
47
 
@@ -52,44 +49,28 @@ module Honeybadger
52
49
  handle_response(msg, notify_backend(msg))
53
50
  end
54
51
 
55
- def shutdown
52
+ def shutdown(force = false)
56
53
  d { 'shutting down worker' }
57
54
 
58
55
  mutex.synchronize do
59
56
  @shutdown = true
60
- @pid = nil
61
- queue.push(SHUTDOWN)
62
- end
63
-
64
- return true unless thread
65
-
66
- r = true
67
- unless Thread.current.eql?(thread)
68
- begin
69
- r = !!thread.join
70
- ensure
71
- shutdown! unless r
72
- end
73
57
  end
74
58
 
75
- r
76
- end
59
+ return true if force
60
+ return true unless thread&.alive?
77
61
 
78
- def shutdown!
79
- mutex.synchronize do
80
- @shutdown = true
81
- @pid = nil
82
- queue.clear
62
+ if throttled?
63
+ warn { sprintf('Unable to report %s error(s) to Honeybadger (currently throttled)', queue.size) } unless queue.empty?
64
+ return true
83
65
  end
84
66
 
85
- d { 'killing worker thread' }
86
-
87
- if thread
88
- Thread.kill(thread)
89
- thread.join # Allow ensure blocks to execute.
90
- end
67
+ info { sprintf('Waiting to report %s error(s) to Honeybadger', queue.size) } unless queue.empty?
91
68
 
92
- true
69
+ queue.push(SHUTDOWN)
70
+ !!thread.join
71
+ ensure
72
+ queue.clear
73
+ kill!
93
74
  end
94
75
 
95
76
  # Blocks until queue is processed up to this point in time.
@@ -109,7 +90,7 @@ module Honeybadger
109
90
  @shutdown = false
110
91
  @start_at = nil
111
92
 
112
- return true if thread && thread.alive?
93
+ return true if thread&.alive?
113
94
 
114
95
  @pid = Process.pid
115
96
  @thread = Thread.new { run }
@@ -120,24 +101,48 @@ module Honeybadger
120
101
 
121
102
  private
122
103
 
123
- attr_reader :config, :queue, :pid, :mutex, :marker,
124
- :thread, :throttles
104
+ attr_reader :config, :queue, :pid, :mutex, :marker, :thread, :throttle,
105
+ :throttle_interval, :start_at
125
106
 
126
107
  def_delegator :config, :backend
127
108
 
109
+ def shutdown?
110
+ mutex.synchronize { @shutdown }
111
+ end
112
+
113
+ def suspended?
114
+ mutex.synchronize { start_at && Time.now.to_i < start_at }
115
+ end
116
+
128
117
  def can_start?
129
- mutex.synchronize do
130
- return true unless @shutdown
131
- return false unless @start_at
132
- Time.now.to_i >= @start_at
118
+ return false if shutdown?
119
+ return false if suspended?
120
+ true
121
+ end
122
+
123
+ def throttled?
124
+ mutex.synchronize { throttle > 0 }
125
+ end
126
+
127
+ def kill!
128
+ d { 'killing worker thread' }
129
+
130
+ if thread
131
+ Thread.kill(thread)
132
+ thread.join # Allow ensure blocks to execute.
133
133
  end
134
+
135
+ true
134
136
  end
135
137
 
136
138
  def suspend(interval)
137
- mutex.synchronize { @start_at = Time.now.to_i + interval }
139
+ mutex.synchronize do
140
+ @start_at = Time.now.to_i + interval
141
+ queue.clear
142
+ end
138
143
 
139
144
  # Must be performed last since this may kill the current thread.
140
- shutdown!
145
+ kill!
141
146
  end
142
147
 
143
148
  def run
@@ -155,7 +160,7 @@ module Honeybadger
155
160
  end
156
161
  rescue Exception => e
157
162
  error {
158
- msg = "error in worker thread (shutting down) class=%s message=%s\n\t%s"
163
+ msg = "Error in worker thread (shutting down) class=%s message=%s\n\t%s"
159
164
  sprintf(msg, e.class, e.message.dump, Array(e.backtrace).join("\n\t"))
160
165
  }
161
166
  ensure
@@ -164,46 +169,54 @@ module Honeybadger
164
169
 
165
170
  def work(msg)
166
171
  send_now(msg)
172
+
173
+ if shutdown? && throttled?
174
+ warn { sprintf('Unable to report %s error(s) to Honeybadger (currently throttled)', queue.size) } if queue.size > 1
175
+ kill!
176
+ return
177
+ end
178
+
167
179
  sleep(throttle_interval)
168
180
  rescue StandardError => e
169
181
  error {
170
182
  msg = "Error in worker thread class=%s message=%s\n\t%s"
171
183
  sprintf(msg, e.class, e.message.dump, Array(e.backtrace).join("\n\t"))
172
184
  }
173
- sleep(1)
174
- end
175
-
176
- def throttle_interval
177
- return 0 unless throttles[0]
178
- mutex.synchronize do
179
- throttles.reduce(1) {|a,e| a*e }
180
- end
181
185
  end
182
186
 
183
187
  def notify_backend(payload)
184
- debug { sprintf('worker notifying backend id=%s', payload.id) }
188
+ d { sprintf('worker notifying backend id=%s', payload.id) }
185
189
  backend.notify(:notices, payload)
186
190
  end
187
191
 
188
- def add_throttle(t)
192
+ def calc_throttle_interval
193
+ ((BASE_THROTTLE ** throttle) - 1).round(3)
194
+ end
195
+
196
+ def inc_throttle
189
197
  mutex.synchronize do
190
- throttles.push(t)
198
+ @throttle += 1
199
+ @throttle_interval = calc_throttle_interval
200
+ throttle
191
201
  end
192
202
  end
193
203
 
194
- def del_throttle
204
+ def dec_throttle
195
205
  mutex.synchronize do
196
- throttles.shift
206
+ return nil if throttle == 0
207
+ @throttle -= 1
208
+ @throttle_interval = calc_throttle_interval
209
+ throttle
197
210
  end
198
211
  end
199
212
 
200
213
  def handle_response(msg, response)
201
- debug { sprintf('worker response code=%s message=%s', response.code, response.message.to_s.dump) }
214
+ d { sprintf('worker response id=%s code=%s message=%s', msg.id, response.code, response.message.to_s.dump) }
202
215
 
203
216
  case response.code
204
217
  when 429, 503
205
- warn { sprintf('Error report failed: project is sending too many errors. id=%s code=%s throttle=1.25 interval=%s', msg.id, response.code, throttle_interval) }
206
- add_throttle(1.25)
218
+ throttle = inc_throttle
219
+ warn { sprintf('Error report failed: project is sending too many errors. id=%s code=%s throttle=%s interval=%s', msg.id, response.code, throttle, throttle_interval) }
207
220
  when 402
208
221
  warn { sprintf('Error report failed: payment is required. id=%s code=%s', msg.id, response.code) }
209
222
  suspend(3600)
@@ -211,8 +224,8 @@ module Honeybadger
211
224
  warn { sprintf('Error report failed: API key is invalid. id=%s code=%s', msg.id, response.code) }
212
225
  suspend(3600)
213
226
  when 201
214
- if throttle = del_throttle
215
- info { sprintf('Success ⚡ https://app.honeybadger.io/notice/%s id=%s code=%s throttle=%s interval=%s', msg.id, msg.id, response.code, throttle_interval, response.code) }
227
+ if throttle = dec_throttle
228
+ info { sprintf('Success ⚡ https://app.honeybadger.io/notice/%s id=%s code=%s throttle=%s interval=%s', msg.id, msg.id, response.code, throttle, throttle_interval) }
216
229
  else
217
230
  info { sprintf('Success ⚡ https://app.honeybadger.io/notice/%s id=%s code=%s', msg.id, msg.id, response.code) }
218
231
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: honeybadger
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.4.2
4
+ version: 4.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Honeybadger Industries LLC
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-08-01 00:00:00.000000000 Z
11
+ date: 2019-08-05 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Make managing application errors a more pleasant experience.
14
14
  email: