honeybadger 4.4.2 → 4.5.0

Sign up to get free protection for your applications and to get access to all the features.
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: