honeybadger 2.0.0.beta.6 → 2.0.0.beta.7

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
  SHA1:
3
- metadata.gz: c9805ffd53c0cada4c33bfbd62bd20d891c4f039
4
- data.tar.gz: 71ab06bd9401356104a2c109d8948fbc54233604
3
+ metadata.gz: ea4729431078cbb1e542a22bbb1ac422a038be32
4
+ data.tar.gz: 7da4b010726bd765840424ad160a8235e04ad5b8
5
5
  SHA512:
6
- metadata.gz: e386d4b064d2df292bd15580045bda368367366d46991abf23b2dd02cd778c00e8dd222e55adb5279dcb4aa05ec5cd59bb4a29d2f845e00fa4aa069c0f3b2022
7
- data.tar.gz: accd30aa1300df10932d20e93da8bc0088532d9baaa37af9e270d025bce3034777861092c01b49b85c797610aeb82a8647d44565695597bc4537a9d0c6dc3d0b
6
+ metadata.gz: 685b29b9328d62dda5c9b5bfda43968601d9f6f7861060c2ad8531c9afcb650e386e1bcf82785ff37a630957b21eeebe8d07ac8b80d406d9d391e52c9b672d3b
7
+ data.tar.gz: 6c503a35df7698efa690e1c8552c8f2c6b829ccdeeca7263ae20ed3b0f4eb3afd20f8fbc4375907fb49587f51c75905c3517b2b8b3b437c8d2acfa20c658dfe9
@@ -1,7 +1,7 @@
1
1
  require 'securerandom'
2
2
 
3
3
  module Honeybadger
4
- class Worker
4
+ class Agent
5
5
  class Batch
6
6
  def initialize(config, name = :data, max = 100, interval = 60, now = now)
7
7
  @id = SecureRandom.uuid
@@ -1,5 +1,5 @@
1
1
  module Honeybadger
2
- class Worker
2
+ class Agent
3
3
  # Internal: A thread-safe first-in-first-out queue. Values are pushed onto
4
4
  # the queue and released at a defined interval.
5
5
  class MeteredQueue
@@ -1,5 +1,5 @@
1
1
  module Honeybadger
2
- class Worker
2
+ class Agent
3
3
  # This code comes from batsd
4
4
  class MetricsCollection < Array
5
5
  PERCENTILE_METHOD_SIGNATURE = /\Apercentile_(.+)\z/.freeze
@@ -2,8 +2,8 @@ require 'securerandom'
2
2
  require 'forwardable'
3
3
 
4
4
  module Honeybadger
5
- class Worker
6
- autoload :MetricsCollection, 'honeybadger/worker/metrics_collection'
5
+ class Agent
6
+ autoload :MetricsCollection, 'honeybadger/agent/metrics_collection'
7
7
 
8
8
  class MetricsCollector
9
9
  extend Forwardable
@@ -46,21 +46,31 @@ module Honeybadger
46
46
  now >= future
47
47
  end
48
48
 
49
- def to_a
50
- [].tap do |m|
51
- metrics[:counter].each do |metric, values|
52
- m << "#{metric} #{values.sum}"
49
+ def size
50
+ mutex.synchronize do
51
+ metrics.reduce(0) do |count, hash|
52
+ count + hash[1].size
53
53
  end
54
- metrics[:timing].each do |metric, values|
55
- m << "#{metric}:mean #{values.mean}"
56
- m << "#{metric}:median #{values.median}"
57
- m << "#{metric}:percentile_90 #{values.percentile(90)}"
58
- m << "#{metric}:min #{values.min}"
59
- m << "#{metric}:max #{values.max}"
60
- m << "#{metric}:stddev #{values.standard_dev}" if values.count > 1
61
- m << "#{metric} #{values.count}"
54
+ end
55
+ end
56
+
57
+ def to_a
58
+ mutex.synchronize do
59
+ [].tap do |m|
60
+ metrics[:counter].each do |metric, values|
61
+ m << "#{metric} #{values.sum}"
62
+ end
63
+ metrics[:timing].each do |metric, values|
64
+ m << "#{metric}:mean #{values.mean}"
65
+ m << "#{metric}:median #{values.median}"
66
+ m << "#{metric}:percentile_90 #{values.percentile(90)}"
67
+ m << "#{metric}:min #{values.min}"
68
+ m << "#{metric}:max #{values.max}"
69
+ m << "#{metric}:stddev #{values.standard_dev}" if values.count > 1
70
+ m << "#{metric} #{values.count}"
71
+ end
72
+ m.compact!
62
73
  end
63
- m.compact!
64
74
  end
65
75
  end
66
76
 
@@ -0,0 +1,26 @@
1
+ module Honeybadger
2
+ class Agent
3
+ # Internal: A default worker which does nothing.
4
+ class NullWorker
5
+ def push(obj)
6
+ true
7
+ end
8
+
9
+ def shutdown(timeout = nil)
10
+ true
11
+ end
12
+
13
+ def shutdown!
14
+ true
15
+ end
16
+
17
+ def flush
18
+ true
19
+ end
20
+
21
+ def start
22
+ true
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,217 @@
1
+ require 'forwardable'
2
+ require 'net/http'
3
+
4
+ require 'honeybadger/logging'
5
+ require 'honeybadger/agent/null_worker'
6
+
7
+ module Honeybadger
8
+ class Agent
9
+ # Internal: A concurrent queue to notify the backend.
10
+ class Worker
11
+ extend Forwardable
12
+
13
+ include Honeybadger::Logging::Helper
14
+
15
+ # Internal: See Agent::Thread
16
+ class Thread < ::Thread; end
17
+
18
+ # Internal: A queue which enforces a maximum size.
19
+ class Queue < ::Queue
20
+ attr_reader :max_size
21
+
22
+ def initialize(max_size)
23
+ @max_size = max_size
24
+ super()
25
+ end
26
+
27
+ def push(obj)
28
+ super unless size == max_size
29
+ end
30
+ end
31
+
32
+ SHUTDOWN = :__hb_worker_shutdown!
33
+
34
+ def initialize(config, feature)
35
+ @config = config
36
+ @feature = feature
37
+ @backend = config.backend
38
+ @throttles = []
39
+ @mutex = Mutex.new
40
+ @marker = ConditionVariable.new
41
+ @queue = Queue.new(1000)
42
+ @shutdown = false
43
+ end
44
+
45
+ def push(obj)
46
+ if start
47
+ queue.push(obj)
48
+ end
49
+ end
50
+
51
+ # Internal: Shutdown the worker.
52
+ #
53
+ # timeout - The Integer timeout to wait before killing thread.
54
+ #
55
+ # Returns false if timeout reached, otherwise true.
56
+ def shutdown(timeout = 3)
57
+ mutex.synchronize do
58
+ @shutdown = true
59
+ @pid = nil
60
+ queue.push(SHUTDOWN)
61
+ end
62
+
63
+ return true unless thread
64
+
65
+ r = true
66
+ unless Thread.current.eql?(thread)
67
+ begin
68
+ r = !!thread.join(timeout)
69
+ ensure
70
+ shutdown! unless r
71
+ end
72
+ end
73
+
74
+ r
75
+ end
76
+
77
+ def shutdown!
78
+ mutex.synchronize do
79
+ @shutdown = true
80
+ @pid = nil
81
+ end
82
+
83
+ d { sprintf('killing worker thread feature=%s', feature) }
84
+
85
+ if thread
86
+ Thread.kill(thread)
87
+ thread.join # Allow ensure blocks to execute.
88
+ end
89
+
90
+ true
91
+ end
92
+
93
+ # Internal: Blocks until queue is processed up to this point in time.
94
+ #
95
+ # Returns nothing.
96
+ def flush
97
+ mutex.synchronize do
98
+ if thread && thread.alive?
99
+ queue.push(marker)
100
+ marker.wait(mutex)
101
+ end
102
+ end
103
+ end
104
+
105
+ def start
106
+ mutex.synchronize do
107
+ return false if @shutdown
108
+ return true if thread && thread.alive?
109
+
110
+ @pid = Process.pid
111
+ @thread = Thread.new { run }
112
+ end
113
+
114
+ true
115
+ end
116
+
117
+ private
118
+
119
+ attr_reader :config, :backend, :feature, :queue, :pid, :mutex, :marker,
120
+ :thread, :throttles
121
+
122
+ def run
123
+ begin
124
+ d { sprintf('worker started feature=%s', feature) }
125
+ loop do
126
+ case msg = queue.pop
127
+ when SHUTDOWN then break
128
+ when ConditionVariable then signal_marker(msg)
129
+ else process(msg)
130
+ end
131
+ end
132
+ ensure
133
+ d { sprintf('stopping worker feature=%s', feature) }
134
+ end
135
+ rescue Exception => e
136
+ error(sprintf('error in worker thread (shutting down) feature=%s class=%s message=%s at=%s', feature, e.class, e.message.dump, e.backtrace.first.dump))
137
+ ensure
138
+ release_marker
139
+ end
140
+
141
+ def process(msg)
142
+ handle_response(notify_backend(msg))
143
+ sleep(throttle_interval)
144
+ rescue StandardError => e
145
+ error(sprintf('error in worker thread feature=%s class=%s message=%s at=%s', feature, e.class, e.message.dump, e.backtrace.first.dump))
146
+ sleep(1)
147
+ end
148
+
149
+ def throttle_interval
150
+ return 0 unless throttles[0]
151
+ mutex.synchronize do
152
+ throttles.reduce(1) {|a,e| a*e }
153
+ end
154
+ end
155
+
156
+ def notify_backend(payload)
157
+ debug { sprintf('worker notifying backend feature=%s id=%s', feature, payload.id) }
158
+ backend.notify(feature, payload)
159
+ end
160
+
161
+ def add_throttle(t)
162
+ mutex.synchronize do
163
+ throttles.push(t)
164
+ end
165
+ end
166
+
167
+ def del_throttle
168
+ mutex.synchronize do
169
+ throttles.shift
170
+ end
171
+ end
172
+
173
+ def handle_response(response)
174
+ debug { sprintf('worker response feature=%s code=%s message=%s', feature, response.code, response.message.to_s.dump) }
175
+
176
+ case response.code
177
+ when 429, 503
178
+ add_throttle(1.25)
179
+ debug { sprintf('worker applying throttle=1.25 interval=%s feature=%s code=%s', throttle_interval, feature, response.code) }
180
+ when 402
181
+ warn { sprintf('worker shutting down (payment required) feature=%s code=%s', feature, response.code) }
182
+ shutdown!
183
+ when 403
184
+ warn { sprintf('worker shutting down (unauthorized) feature=%s code=%s', feature, response.code) }
185
+ shutdown!
186
+ when 201
187
+ if throttle = del_throttle
188
+ debug { sprintf('worker removing throttle=%s interval=%s feature=%s code=%s', throttle, throttle_interval, feature, response.code) }
189
+ end
190
+ when :error
191
+ # Error logged by backend.
192
+ else
193
+ warn { sprintf('worker unknown response feature=%s code=%s', feature, response.code) }
194
+ end
195
+ end
196
+
197
+ # Internal: Release the marker. Important to perform during cleanup when
198
+ # shutting down, otherwise it could end up waiting indefinitely.
199
+ #
200
+ # Returns nothing.
201
+ def release_marker
202
+ signal_marker(marker)
203
+ end
204
+
205
+ # Internal: Signal a marker.
206
+ #
207
+ # marker - The ConditionVariable marker to signal.
208
+ #
209
+ # Returns nothing.
210
+ def signal_marker(marker)
211
+ mutex.synchronize do
212
+ marker.signal
213
+ end
214
+ end
215
+ end
216
+ end
217
+ end
@@ -2,18 +2,25 @@ require 'forwardable'
2
2
 
3
3
  require 'honeybadger/version'
4
4
  require 'honeybadger/config'
5
- require 'honeybadger/worker'
6
5
  require 'honeybadger/notice'
7
6
  require 'honeybadger/plugin'
8
7
  require 'honeybadger/logging'
9
8
 
10
9
  module Honeybadger
11
- # Internal: A broker for the configuration and the worker.
10
+ # Internal: A broker for the configuration and the workers.
12
11
  class Agent
13
12
  extend Forwardable
14
13
 
15
14
  include Logging::Helper
16
15
 
16
+ # Internal: Sub-class thread so we have a named thread (useful for debugging in Thread.list).
17
+ class Thread < ::Thread; end
18
+
19
+ autoload :Worker, 'honeybadger/agent/worker'
20
+ autoload :NullWorker, 'honeybadger/agent/worker'
21
+ autoload :Batch, 'honeybadger/agent/batch'
22
+ autoload :MetricsCollector, 'honeybadger/agent/metrics_collector'
23
+
17
24
  class << self
18
25
  extend Forwardable
19
26
 
@@ -64,7 +71,8 @@ module Honeybadger
64
71
  config.logger.info("Starting Honeybadger version #{VERSION}")
65
72
  load_plugins(config)
66
73
  @instance = new(config)
67
- @instance.start
74
+
75
+ true
68
76
  end
69
77
 
70
78
  def self.stop(*args)
@@ -88,6 +96,16 @@ module Honeybadger
88
96
  self.instance ? self.instance.increment(*args) : false
89
97
  end
90
98
 
99
+ def self.flush(&block)
100
+ if self.instance
101
+ self.instance.flush(&block)
102
+ elsif !block_given?
103
+ false
104
+ else
105
+ yield
106
+ end
107
+ end
108
+
91
109
  # Internal: Callback to perform after agent has been stopped at_exit.
92
110
  #
93
111
  # block - An optional block to execute.
@@ -112,9 +130,21 @@ module Honeybadger
112
130
  end
113
131
  end
114
132
 
133
+ attr_reader :delay, :workers, :pid, :thread, :traces, :metrics
134
+
115
135
  def initialize(config)
116
136
  @config = config
117
- @worker = Worker.new(config)
137
+ @delay = config.debug? ? 10 : 60
138
+ @mutex = Mutex.new
139
+ @pid = Process.pid
140
+
141
+ unless config.backend.kind_of?(Backend::Server)
142
+ warn('Initializing development backend: data will not be reported.')
143
+ end
144
+
145
+ init_workers
146
+ init_traces
147
+ init_metrics
118
148
 
119
149
  at_exit do
120
150
  stop
@@ -122,14 +152,47 @@ module Honeybadger
122
152
  end
123
153
  end
124
154
 
125
- def_delegators :@worker, :stop, :fork, :trace, :timing, :increment
126
-
155
+ # Internal: Spawn the agent thread. This method is idempotent.
156
+ #
157
+ # Returns false if the Agent is stopped, otherwise true.
127
158
  def start
128
- unless worker.backend.kind_of?(Backend::Server)
129
- warn('Initializing development backend: data will not be reported.')
159
+ mutex.synchronize do
160
+ return false unless pid
161
+ return true if thread && thread.alive?
162
+
163
+ debug { 'starting agent' }
164
+
165
+ @pid = Process.pid
166
+ @thread = Thread.new { run }
130
167
  end
131
168
 
132
- worker.start
169
+ true
170
+ end
171
+
172
+ def stop(force = false)
173
+ debug { 'stopping agent' }
174
+
175
+ mutex.synchronize do
176
+ @pid = nil
177
+ end
178
+
179
+ # Kill the collector
180
+ Thread.kill(thread) if thread
181
+
182
+ unless force
183
+ flush_traces
184
+ flush_metrics
185
+ end
186
+
187
+ workers.each_pair do |key, worker|
188
+ worker.send(force ? :shutdown! : :shutdown)
189
+ end
190
+
191
+ true
192
+ end
193
+
194
+ def fork
195
+ # noop
133
196
  end
134
197
 
135
198
  def notice(opts)
@@ -141,13 +204,122 @@ module Honeybadger
141
204
  false
142
205
  else
143
206
  debug { sprintf('notice feature=notices id=%s', notice.id) }
144
- worker.notice(notice)
207
+ workers[:notices].push(notice)
145
208
  notice.id
146
209
  end
147
210
  end
148
211
 
212
+ def trace(trace)
213
+ start
214
+
215
+ if trace.duration > config[:'traces.threshold']
216
+ debug { sprintf('agent adding trace duration=%s feature=traces id=%s', trace.duration.round(2), trace.id) }
217
+ mutex.synchronize { traces.push(trace) }
218
+ flush_traces if traces.flush?
219
+ true
220
+ else
221
+ debug { sprintf('agent discarding trace duration=%s feature=traces id=%s', trace.duration.round(2), trace.id) }
222
+ false
223
+ end
224
+ end
225
+
226
+ def timing(*args, &block)
227
+ start
228
+
229
+ mutex.synchronize { metrics.timing(*args, &block) }
230
+ flush_metrics if metrics.flush?
231
+
232
+ true
233
+ end
234
+
235
+ def increment(*args, &block)
236
+ start
237
+
238
+ mutex.synchronize { metrics.increment(*args, &block) }
239
+ flush_metrics if metrics.flush?
240
+
241
+ true
242
+ end
243
+
244
+ # Internal: Flush the workers. See Honeybadger#flush.
245
+ #
246
+ # block - an option block which is executed before flushing data.
247
+ #
248
+ # Returns value from block if block is given, otherwise true.
249
+ def flush
250
+ return true unless block_given?
251
+ yield
252
+ ensure
253
+ flush_metrics
254
+ flush_traces
255
+ workers.values.each(&:flush)
256
+ end
257
+
149
258
  private
150
259
 
151
- attr_reader :worker, :config
260
+ attr_reader :config, :mutex
261
+
262
+ def push(feature, object)
263
+ unless config.features[feature]
264
+ debug { sprintf('agent dropping feature=%s reason=ping', feature) }
265
+ return false
266
+ end
267
+
268
+ workers[feature].push(object)
269
+
270
+ true
271
+ end
272
+
273
+ def run
274
+ loop { work }
275
+ rescue Exception => e
276
+ error(sprintf('error in agent thread (shutting down) class=%s message=%s at=%s', e.class, e.message.dump, e.backtrace.first.dump))
277
+ ensure
278
+ d { sprintf('stopping agent') }
279
+ end
280
+
281
+ def work
282
+ flush_metrics if metrics.flush?
283
+ flush_traces if traces.flush?
284
+ rescue StandardError => e
285
+ error(sprintf('error in agent thread class=%s message=%s at=%s', e.class, e.message.dump, e.backtrace.first.dump))
286
+ ensure
287
+ sleep(delay)
288
+ end
289
+
290
+ def init_workers
291
+ @workers = Hash.new(NullWorker.new)
292
+ workers[:notices] = Worker.new(config, :notices)
293
+ workers[:traces] = Worker.new(config, :traces)
294
+ workers[:metrics] = Worker.new(config, :metrics)
295
+ end
296
+
297
+ def init_traces
298
+ @traces = Batch.new(config, :traces, 20, config.debug? ? 10 : 60)
299
+ end
300
+
301
+ def init_metrics
302
+ @metrics = MetricsCollector.new(config, config.debug? ? 10 : 60)
303
+ end
304
+
305
+ def flush_metrics
306
+ mutex.synchronize do
307
+ if (count = metrics.size) > 0
308
+ debug { sprintf('agent flushing metrics feature=metrics count=%d', count) }
309
+ end
310
+ metrics.chunk(100, &method(:push).to_proc.curry[:metrics])
311
+ init_metrics
312
+ end
313
+ end
314
+
315
+ def flush_traces
316
+ mutex.synchronize do
317
+ if (count = traces.size) > 0
318
+ debug { sprintf('agent flushing traces feature=traces count=%d', count) }
319
+ end
320
+ push(:traces, traces) unless traces.empty?
321
+ init_traces
322
+ end
323
+ end
152
324
  end
153
325
  end
@@ -28,6 +28,7 @@ module Honeybadger
28
28
  def self.parse(unparsed_line, opts = {})
29
29
  filters = opts[:filters] || []
30
30
  filtered_line = filters.reduce(unparsed_line) do |line, proc|
31
+ # TODO: Break if nil
31
32
  if proc.arity == 2
32
33
  proc.call(line, opts[:config])
33
34
  else
@@ -69,6 +69,10 @@ module Honeybadger
69
69
  description: 'The log level.',
70
70
  default: 'INFO'
71
71
  },
72
+ :'logging.debug' => {
73
+ description: 'Override debug logging.',
74
+ default: nil
75
+ },
72
76
  :'logging.tty_level' => {
73
77
  description: 'Level to log when attached to a terminal (anything < logging.level will always be ignored).',
74
78
  default: 'DEBUG'
@@ -79,10 +79,14 @@ module Honeybadger
79
79
  Backend.for((self[:backend] || default_backend).to_sym).new(self)
80
80
  end
81
81
 
82
+ def dev?
83
+ self[:env] && Array(self[:development_environments]).include?(self[:env])
84
+ end
85
+
82
86
  def public?
83
87
  return true if self[:report_data]
84
88
  return false if self[:report_data] == false
85
- !self[:env] || !Array(self[:development_environments]).include?(self[:env])
89
+ !self[:env] || !dev?
86
90
  end
87
91
 
88
92
  def default_backend
@@ -98,7 +102,12 @@ module Honeybadger
98
102
  end
99
103
 
100
104
  def debug?
101
- self[:debug]
105
+ !!self[:debug]
106
+ end
107
+
108
+ def log_debug?
109
+ return debug? if self[:'logging.debug'].nil?
110
+ !!self[:'logging.debug']
102
111
  end
103
112
 
104
113
  # Internal: Optional path to honeybadger.log log file. If nil, STDOUT will be used
@@ -7,7 +7,6 @@ module Honeybadger
7
7
  autoload :Config, 'honeybadger/config'
8
8
  autoload :Logging, 'honeybadger/logging'
9
9
  autoload :Notice, 'honeybadger/notice'
10
- autoload :Worker, 'honeybadger/worker'
11
10
  autoload :Trace, 'honeybadger/trace'
12
11
  autoload :Plugin, 'honeybadger/plugin'
13
12
 
@@ -11,6 +11,7 @@ module Honeybadger
11
11
  # method is defined/block captured in this module rather than delegating to
12
12
  # the logger directly to avoid extra object allocation.
13
13
  module Helper
14
+ private
14
15
  def debug(msg = nil)
15
16
  return true unless logger.debug?
16
17
  msg = yield if block_given?
@@ -134,7 +135,7 @@ module Honeybadger
134
135
  end
135
136
 
136
137
  def debug?
137
- @config.debug?
138
+ @config.log_debug?
138
139
  end
139
140
 
140
141
  private
@@ -1,4 +1,4 @@
1
1
  module Honeybadger
2
2
  # Public: The current String Honeybadger version.
3
- VERSION = '2.0.0.beta.6'.freeze
3
+ VERSION = '2.0.0.beta.7'.freeze
4
4
  end
data/lib/honeybadger.rb CHANGED
@@ -171,6 +171,40 @@ module Honeybadger
171
171
  def clear!
172
172
  Thread.current[:__honeybadger_context] = nil
173
173
  end
174
+
175
+ # Public: Flushes all data from workers before returning. This is most useful
176
+ # in tests when using the test backend, where normally the asynchronous
177
+ # nature of this library could create race conditions.
178
+ #
179
+ # block - The optional block to execute (exceptions will propagate after data
180
+ # is flushed).
181
+ #
182
+ # Examples:
183
+ #
184
+ # # Without a block:
185
+ # it "sends a notification to Honeybadger" do
186
+ # expect {
187
+ # Honeybadger.notify(StandardError.new('test backend'))
188
+ # Honeybadger.flush
189
+ # }.to change(Honeybadger::Backend::Test.notifications[:notices], :size).by(1)
190
+ # end
191
+ #
192
+ # # With a block:
193
+ # it "sends a notification to Honeybadger" do
194
+ # expect {
195
+ # Honeybadger.flush do
196
+ # 50.times do
197
+ # Honeybadger.notify(StandardError.new('test backend'))
198
+ # end
199
+ # end
200
+ # }.to change(Honeybadger::Backend::Test.notifications[:notices], :size).by(50)
201
+ # end
202
+ #
203
+ # Returns value of block if block is given, otherwise true on success or
204
+ # false if Honeybadger isn't running.
205
+ def flush(&block)
206
+ Agent.flush(&block)
207
+ end
174
208
  end
175
209
 
176
210
  if defined?(::Rails::Railtie)
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: 2.0.0.beta.6
4
+ version: 2.0.0.beta.7
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: 2014-09-25 00:00:00.000000000 Z
11
+ date: 2014-10-08 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Make managing application errors a more pleasant experience.
14
14
  email:
@@ -23,6 +23,12 @@ files:
23
23
  - bin/honeybadger
24
24
  - lib/honeybadger.rb
25
25
  - lib/honeybadger/agent.rb
26
+ - lib/honeybadger/agent/batch.rb
27
+ - lib/honeybadger/agent/metered_queue.rb
28
+ - lib/honeybadger/agent/metrics_collection.rb
29
+ - lib/honeybadger/agent/metrics_collector.rb
30
+ - lib/honeybadger/agent/null_worker.rb
31
+ - lib/honeybadger/agent/worker.rb
26
32
  - lib/honeybadger/backend.rb
27
33
  - lib/honeybadger/backend/base.rb
28
34
  - lib/honeybadger/backend/debug.rb
@@ -64,11 +70,6 @@ files:
64
70
  - lib/honeybadger/util/sanitizer.rb
65
71
  - lib/honeybadger/util/stats.rb
66
72
  - lib/honeybadger/version.rb
67
- - lib/honeybadger/worker.rb
68
- - lib/honeybadger/worker/batch.rb
69
- - lib/honeybadger/worker/metered_queue.rb
70
- - lib/honeybadger/worker/metrics_collection.rb
71
- - lib/honeybadger/worker/metrics_collector.rb
72
73
  - resources/ca-bundle.crt
73
74
  - vendor/capistrano-honeybadger/lib/capistrano/honeybadger.rb
74
75
  - vendor/capistrano-honeybadger/lib/capistrano/tasks/deploy.cap
@@ -1,237 +0,0 @@
1
- require 'forwardable'
2
- require 'net/http'
3
-
4
- require 'honeybadger/logging'
5
-
6
- module Honeybadger
7
- class Worker
8
- extend Forwardable
9
-
10
- include Honeybadger::Logging::Helper
11
-
12
- autoload :Batch, 'honeybadger/worker/batch'
13
- autoload :MetricsCollector, 'honeybadger/worker/metrics_collector'
14
- autoload :MeteredQueue, 'honeybadger/worker/metered_queue'
15
-
16
- # Sub-class thread so we have a named thread (useful for debugging in Thread.list).
17
- class Thread < ::Thread; end
18
-
19
- attr_reader :backend, :queue, :features, :metrics, :traces, :pid, :mutex, :thread
20
-
21
- def initialize(config)
22
- @backend = config.backend
23
- @config = config
24
- @mutex = Mutex.new
25
- prepare
26
- end
27
-
28
- def start
29
- debug { 'starting worker' }
30
-
31
- @pid = Process.pid
32
- @thread = Thread.new { run }
33
-
34
- true
35
- end
36
-
37
- def stop(force = false)
38
- debug { 'stopping worker' }
39
- if thread
40
- if force
41
- debug { 'killing worker' }
42
- Thread.kill(thread)
43
- else
44
- thread[:should_exit] = true
45
- unless thread.eql?(Thread.current)
46
- mutex.unlock if mutex.locked?
47
- thread.join
48
- end
49
- end
50
- end
51
- @thread = nil
52
- @pid = nil
53
- end
54
-
55
- def fork
56
- debug { 'forking worker' }
57
-
58
- stop
59
-
60
- mutex.synchronize { prepare }
61
-
62
- start
63
- end
64
-
65
- def notice(notice)
66
- debug { sprintf('worker adding notice feature=notices id=%s', notice.id) }
67
- push(:notices, notice)
68
- end
69
-
70
- def trace(trace)
71
- if trace.duration > config[:'traces.threshold']
72
- debug { sprintf('worker adding trace duration=%s feature=traces id=%s', trace.duration.round(2), trace.id) }
73
- traces.push(trace)
74
- flush_traces if traces.flush?
75
- true
76
- else
77
- debug { sprintf('worker discarding trace duration=%s feature=traces id=%s', trace.duration.round(2), trace.id) }
78
- false
79
- end
80
- end
81
-
82
- def timing(*args, &block)
83
- metrics.timing(*args, &block)
84
- flush_metrics if metrics.flush?
85
- true
86
- end
87
-
88
- def increment(*args, &block)
89
- metrics.increment(*args, &block)
90
- flush_metrics if metrics.flush?
91
- true
92
- end
93
-
94
- private
95
-
96
- attr_reader :config
97
-
98
- def init_queue
99
- @queue = {
100
- notices: MeteredQueue.new,
101
- metrics: MeteredQueue.new,
102
- traces: MeteredQueue.new
103
- }.freeze
104
-
105
- @features = {
106
- notices: true,
107
- metrics: true,
108
- traces: true
109
- }.freeze
110
- end
111
-
112
- def init_traces
113
- @traces = Batch.new(config, :traces, 20, config[:debug] ? 10 : 60)
114
- end
115
-
116
- def init_metrics
117
- @metrics = MetricsCollector.new(config, config[:debug] ? 10 : 60)
118
- end
119
-
120
- def flush_metrics
121
- debug { 'worker flushing metrics feature=metrics' } # TODO: Include count.
122
- mutex.synchronize do
123
- metrics.chunk(100, &method(:push).to_proc.curry[:metrics])
124
- init_metrics
125
- end
126
- end
127
-
128
- def flush_traces
129
- debug { sprintf('worker flushing traces feature=traces count=%d', traces.size) }
130
- mutex.synchronize do
131
- push(:traces, traces) unless traces.empty?
132
- init_traces
133
- end
134
- end
135
-
136
- def flush_queue
137
- mutex.synchronize do
138
- queue.each_pair do |feature, queue|
139
- while payload = queue.pop!
140
- handle_response(feature, notify_backend(feature, payload))
141
- end
142
- end
143
- end
144
- end
145
-
146
- def prepare
147
- init_queue
148
- init_metrics
149
- init_traces
150
- end
151
-
152
- def push(feature, object)
153
- unless features[feature]
154
- debug { sprintf('worker dropping feature=%s reason=collector', feature) }
155
- return false
156
- end
157
-
158
- unless config.features[feature]
159
- debug { sprintf('worker dropping feature=%s reason=ping', feature) }
160
- return false
161
- end
162
-
163
- queue[feature].push(object)
164
-
165
- true
166
- end
167
-
168
- def run
169
- begin
170
- debug { 'worker started' }
171
- work until finish
172
- rescue Exception => e
173
- error(sprintf('error in worker thread (shutting down) class=%s message=%s at=%s', e.class, e.message.dump, e.backtrace.first.dump))
174
- raise e
175
- ensure
176
- debug { 'stopping worker' }
177
- end
178
- end
179
-
180
- def work
181
- flush_metrics if metrics.flush?
182
- flush_traces if traces.flush?
183
-
184
- queue.each_pair do |feature, queue|
185
- if payload = queue.pop
186
- handle_response(feature, notify_backend(feature, payload))
187
- end
188
- end
189
-
190
- sleep(0.1)
191
- rescue StandardError => e
192
- error(sprintf('error in worker thread class=%s message=%s at=%s', e.class, e.message.dump, e.backtrace.first.dump))
193
- sleep(1)
194
- end
195
-
196
- def finish
197
- if Thread.current[:should_exit]
198
- debug { 'flushing worker data' }
199
-
200
- flush_metrics
201
- flush_traces
202
- flush_queue
203
-
204
- true
205
- end
206
- end
207
-
208
- def notify_backend(feature, payload)
209
- debug { sprintf('worker notifying backend feature=%s id=%s', feature, payload.id) }
210
- backend.notify(feature, payload)
211
- end
212
-
213
- def handle_response(feature, response)
214
- debug { sprintf('worker response feature=%s code=%s message=%s', feature, response.code, response.message.to_s.dump) }
215
-
216
- case response.code
217
- when 429, 503
218
- debug { sprintf('worker applying throttle=1.25 feature=%s code=%s', feature, response.code) }
219
- queue[feature].throttle(1.25)
220
- when 402
221
- warn { sprintf('worker disabling feature=%s code=%s', feature, response.code) }
222
- mutex.synchronize { features[feature] = false }
223
- when 403
224
- error { sprintf('worker shutting down (unauthorized) feature=%s code=%s', feature, response.code) }
225
- Honeybadger::Agent.stop(true)
226
- when 201
227
- if throttle = queue[feature].unthrottle
228
- debug { sprintf('worker removing throttle=%s feature=%s code=%s', throttle, feature, response.code) }
229
- end
230
- when :error
231
- # Error logged by backend.
232
- else
233
- warn { sprintf('worker unknown response feature=%s code=%s', feature, response.code) }
234
- end
235
- end
236
- end
237
- end