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 +4 -4
- data/lib/honeybadger/{worker → agent}/batch.rb +1 -1
- data/lib/honeybadger/{worker → agent}/metered_queue.rb +1 -1
- data/lib/honeybadger/{worker → agent}/metrics_collection.rb +1 -1
- data/lib/honeybadger/{worker → agent}/metrics_collector.rb +25 -15
- data/lib/honeybadger/agent/null_worker.rb +26 -0
- data/lib/honeybadger/agent/worker.rb +217 -0
- data/lib/honeybadger/agent.rb +183 -11
- data/lib/honeybadger/backtrace.rb +1 -0
- data/lib/honeybadger/config/defaults.rb +4 -0
- data/lib/honeybadger/config.rb +11 -2
- data/lib/honeybadger/const.rb +0 -1
- data/lib/honeybadger/logging.rb +2 -1
- data/lib/honeybadger/version.rb +1 -1
- data/lib/honeybadger.rb +34 -0
- metadata +8 -7
- data/lib/honeybadger/worker.rb +0 -237
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ea4729431078cbb1e542a22bbb1ac422a038be32
|
4
|
+
data.tar.gz: 7da4b010726bd765840424ad160a8235e04ad5b8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 685b29b9328d62dda5c9b5bfda43968601d9f6f7861060c2ad8531c9afcb650e386e1bcf82785ff37a630957b21eeebe8d07ac8b80d406d9d391e52c9b672d3b
|
7
|
+
data.tar.gz: 6c503a35df7698efa690e1c8552c8f2c6b829ccdeeca7263ae20ed3b0f4eb3afd20f8fbc4375907fb49587f51c75905c3517b2b8b3b437c8d2acfa20c658dfe9
|
@@ -2,8 +2,8 @@ require 'securerandom'
|
|
2
2
|
require 'forwardable'
|
3
3
|
|
4
4
|
module Honeybadger
|
5
|
-
class
|
6
|
-
autoload :MetricsCollection, 'honeybadger/
|
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
|
50
|
-
|
51
|
-
metrics
|
52
|
-
|
49
|
+
def size
|
50
|
+
mutex.synchronize do
|
51
|
+
metrics.reduce(0) do |count, hash|
|
52
|
+
count + hash[1].size
|
53
53
|
end
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
data/lib/honeybadger/agent.rb
CHANGED
@@ -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
|
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
|
-
|
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
|
-
@
|
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
|
-
|
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
|
-
|
129
|
-
|
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
|
-
|
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
|
-
|
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 :
|
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
|
@@ -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'
|
data/lib/honeybadger/config.rb
CHANGED
@@ -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] || !
|
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
|
data/lib/honeybadger/const.rb
CHANGED
@@ -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
|
|
data/lib/honeybadger/logging.rb
CHANGED
@@ -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.
|
138
|
+
@config.log_debug?
|
138
139
|
end
|
139
140
|
|
140
141
|
private
|
data/lib/honeybadger/version.rb
CHANGED
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.
|
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-
|
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
|
data/lib/honeybadger/worker.rb
DELETED
@@ -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
|