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

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
  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