honeybadger 2.0.12 → 2.1.0.beta.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +73 -0
- data/lib/honeybadger/agent/metrics_collection.rb +6 -8
- data/lib/honeybadger/agent/worker.rb +28 -8
- data/lib/honeybadger/backend/debug.rb +1 -0
- data/lib/honeybadger/cli/main.rb +2 -0
- data/lib/honeybadger/config.rb +12 -6
- data/lib/honeybadger/config/defaults.rb +124 -51
- data/lib/honeybadger/config/env.rb +13 -18
- data/lib/honeybadger/config/yaml.rb +2 -0
- data/lib/honeybadger/init/rails.rb +28 -10
- data/lib/honeybadger/init/sinatra.rb +16 -6
- data/lib/honeybadger/notice.rb +52 -97
- data/lib/honeybadger/plugins/delayed_job/plugin.rb +21 -8
- data/lib/honeybadger/plugins/local_variables.rb +9 -0
- data/lib/honeybadger/plugins/net_http.rb +4 -1
- data/lib/honeybadger/plugins/resque.rb +63 -0
- data/lib/honeybadger/plugins/sidekiq.rb +7 -4
- data/lib/honeybadger/trace.rb +46 -14
- data/lib/honeybadger/util/request_payload.rb +36 -0
- data/lib/honeybadger/util/sanitizer.rb +11 -1
- data/lib/honeybadger/util/stats.rb +22 -3
- data/lib/honeybadger/version.rb +1 -1
- data/vendor/capistrano-honeybadger/lib/capistrano/tasks/deploy.cap +1 -1
- data/vendor/capistrano-honeybadger/lib/honeybadger/capistrano/legacy.rb +1 -1
- metadata +6 -4
@@ -8,31 +8,26 @@ module Honeybadger
|
|
8
8
|
def initialize(env = ENV)
|
9
9
|
env.each_pair do |k,v|
|
10
10
|
next unless k.match(CONFIG_KEY)
|
11
|
-
next
|
12
|
-
self[
|
11
|
+
next unless config_key = CONFIG_MAPPING[$1]
|
12
|
+
self[config_key] = cast_value(v, OPTIONS[config_key][:type])
|
13
13
|
end
|
14
14
|
end
|
15
15
|
|
16
16
|
private
|
17
17
|
|
18
|
-
def cast_value(value)
|
19
|
-
|
20
|
-
return value.split(ARRAY_VALUES).map(&method(:cast_value))
|
21
|
-
end
|
18
|
+
def cast_value(value, type = String)
|
19
|
+
v = value.to_s
|
22
20
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
value.to_i
|
32
|
-
when /\A\d+\.\d+\z/
|
33
|
-
value.to_f
|
21
|
+
if type == Boolean
|
22
|
+
!!(v =~ /\A(true|t|1)\z/i)
|
23
|
+
elsif type == Array
|
24
|
+
v.split(ARRAY_VALUES).map(&method(:cast_value))
|
25
|
+
elsif type == Integer
|
26
|
+
v.to_i
|
27
|
+
elsif type == Float
|
28
|
+
v.to_f
|
34
29
|
else
|
35
|
-
|
30
|
+
v
|
36
31
|
end
|
37
32
|
end
|
38
33
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
require 'rails'
|
2
2
|
require 'yaml'
|
3
|
+
require 'honeybadger/util/sanitizer'
|
4
|
+
require 'honeybadger/util/request_payload'
|
3
5
|
|
4
6
|
module Honeybadger
|
5
7
|
module Init
|
@@ -30,7 +32,7 @@ module Honeybadger
|
|
30
32
|
Trace.current.add_query(event) if Trace.current and event.name != 'SCHEMA'
|
31
33
|
end
|
32
34
|
|
33
|
-
ActiveSupport::Notifications.subscribe(/^render_(template|action|collection)\.action_view/) do |*args|
|
35
|
+
ActiveSupport::Notifications.subscribe(/^render_(template|partial|action|collection)\.action_view/) do |*args|
|
34
36
|
event = ActiveSupport::Notifications::Event.new(*args)
|
35
37
|
Trace.current.add(event) if Trace.current
|
36
38
|
end
|
@@ -43,7 +45,10 @@ module Honeybadger
|
|
43
45
|
ActiveSupport::Notifications.subscribe('process_action.action_controller') do |*args|
|
44
46
|
event = ActiveSupport::Notifications::Event.new(*args)
|
45
47
|
if Trace.current && event.payload[:controller] && event.payload[:action]
|
46
|
-
|
48
|
+
payload = { source: 'web' }
|
49
|
+
payload[:path] = Util::Sanitizer.new(filters: config.params_filters).filter_url(event.payload[:path]) if event.payload[:path]
|
50
|
+
payload[:request] = request_data(event, config)
|
51
|
+
Trace.current.complete(event, payload)
|
47
52
|
end
|
48
53
|
end
|
49
54
|
end
|
@@ -53,14 +58,6 @@ module Honeybadger
|
|
53
58
|
event = ActiveSupport::Notifications::Event.new(*args)
|
54
59
|
status = event.payload[:exception] ? 500 : event.payload[:status]
|
55
60
|
Agent.timing("app.request.#{status}", event.duration)
|
56
|
-
|
57
|
-
controller = event.payload[:controller]
|
58
|
-
action = event.payload[:action]
|
59
|
-
if controller && action
|
60
|
-
Agent.timing("app.controller.#{controller}.#{action}.total", event.duration)
|
61
|
-
Agent.timing("app.controller.#{controller}.#{action}.view", event.payload[:view_runtime]) if event.payload[:view_runtime]
|
62
|
-
Agent.timing("app.controller.#{controller}.#{action}.db", event.payload[:db_runtime]) if event.payload[:db_runtime]
|
63
|
-
end
|
64
61
|
end
|
65
62
|
end
|
66
63
|
end
|
@@ -77,6 +74,27 @@ module Honeybadger
|
|
77
74
|
:framework => :rails
|
78
75
|
}
|
79
76
|
end
|
77
|
+
|
78
|
+
def request_data(event, config)
|
79
|
+
h = {
|
80
|
+
url: event.payload[:path],
|
81
|
+
component: event.payload[:controller],
|
82
|
+
action: event.payload[:action],
|
83
|
+
params: event.payload[:params]
|
84
|
+
}
|
85
|
+
h.merge!(config.request_hash)
|
86
|
+
h.delete_if {|k,v| config.excluded_request_keys.include?(k) }
|
87
|
+
h[:sanitizer] = Util::Sanitizer.new(filters: config.params_filters)
|
88
|
+
Util::RequestPayload.build(h).update({
|
89
|
+
context: context_data
|
90
|
+
})
|
91
|
+
end
|
92
|
+
|
93
|
+
def context_data
|
94
|
+
if Thread.current[:__honeybadger_context]
|
95
|
+
Util::Sanitizer.new.sanitize(Thread.current[:__honeybadger_context])
|
96
|
+
end
|
97
|
+
end
|
80
98
|
end
|
81
99
|
end
|
82
100
|
end
|
@@ -4,12 +4,7 @@ module Honeybadger
|
|
4
4
|
::Sinatra::Base.class_eval do
|
5
5
|
class << self
|
6
6
|
def build_with_honeybadger(*args, &block)
|
7
|
-
|
8
|
-
if Honeybadger.start(config)
|
9
|
-
use(Honeybadger::Rack::ErrorNotifier, config) if config.feature?(:notices) && config[:'exceptions.enabled']
|
10
|
-
use(Honeybadger::Rack::MetricsReporter, config) if config.feature?(:metrics) && config[:'metrics.enabled']
|
11
|
-
end
|
12
|
-
|
7
|
+
install_honeybadger
|
13
8
|
build_without_honeybadger(*args, &block)
|
14
9
|
end
|
15
10
|
alias :build_without_honeybadger :build
|
@@ -20,6 +15,21 @@ module Honeybadger
|
|
20
15
|
api_key: defined?(honeybadger_api_key) ? honeybadger_api_key : nil
|
21
16
|
}
|
22
17
|
end
|
18
|
+
|
19
|
+
def install_honeybadger
|
20
|
+
config = Honeybadger::Config.new(honeybadger_config(self))
|
21
|
+
|
22
|
+
return unless config[:'sinatra.enabled']
|
23
|
+
return unless Honeybadger.start(config)
|
24
|
+
|
25
|
+
install_honeybadger_middleware(Honeybadger::Rack::ErrorNotifier, config) if config.feature?(:notices) && config[:'exceptions.enabled']
|
26
|
+
install_honeybadger_middleware(Honeybadger::Rack::MetricsReporter, config) if config.feature?(:metrics) && config[:'metrics.enabled']
|
27
|
+
end
|
28
|
+
|
29
|
+
def install_honeybadger_middleware(klass, config)
|
30
|
+
return if middleware.any? {|m| m[0] == klass }
|
31
|
+
use(klass, config)
|
32
|
+
end
|
23
33
|
end
|
24
34
|
end
|
25
35
|
end
|
data/lib/honeybadger/notice.rb
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
require 'json'
|
2
2
|
require 'securerandom'
|
3
3
|
require 'forwardable'
|
4
|
-
require 'ostruct'
|
5
4
|
|
6
5
|
require 'honeybadger/version'
|
7
6
|
require 'honeybadger/backtrace'
|
8
7
|
require 'honeybadger/util/stats'
|
9
8
|
require 'honeybadger/util/sanitizer'
|
10
|
-
require 'honeybadger/
|
9
|
+
require 'honeybadger/util/request_payload'
|
11
10
|
|
12
11
|
module Honeybadger
|
13
12
|
NOTIFIER = {
|
@@ -32,16 +31,6 @@ module Honeybadger
|
|
32
31
|
# Internal: Matches lines beginning with ./
|
33
32
|
RELATIVE_ROOT = Regexp.new('^\.\/').freeze
|
34
33
|
|
35
|
-
# Internal: default values to use for request data.
|
36
|
-
REQUEST_DEFAULTS = {
|
37
|
-
url: nil,
|
38
|
-
component: nil,
|
39
|
-
action: nil,
|
40
|
-
params: {}.freeze,
|
41
|
-
session: {}.freeze,
|
42
|
-
cgi_data: {}.freeze
|
43
|
-
}.freeze
|
44
|
-
|
45
34
|
MAX_EXCEPTION_CAUSES = 5
|
46
35
|
|
47
36
|
class Notice
|
@@ -79,24 +68,25 @@ module Honeybadger
|
|
79
68
|
attr_reader :source
|
80
69
|
|
81
70
|
# Public: CGI variables such as HTTP_METHOD.
|
82
|
-
|
71
|
+
def cgi_data; @request[:cgi_data]; end
|
83
72
|
|
84
73
|
# Public: A hash of parameters from the query string or post body.
|
85
|
-
|
74
|
+
def params; @request[:params]; end
|
86
75
|
alias_method :parameters, :params
|
87
76
|
|
88
77
|
# Public: The component (if any) which was used in this request. (usually the controller)
|
89
|
-
|
78
|
+
def component; @request[:component]; end
|
90
79
|
alias_method :controller, :component
|
91
80
|
|
92
81
|
# Public: The action (if any) that was called in this request.
|
93
|
-
|
82
|
+
def action; @request[:action]; end
|
94
83
|
|
95
84
|
# Public: A hash of session data from the request.
|
96
85
|
def_delegator :@request, :session
|
86
|
+
def session; @request[:session]; end
|
97
87
|
|
98
88
|
# Public: The URL at which the error occurred (if any).
|
99
|
-
|
89
|
+
def url; @request[:url]; end
|
100
90
|
|
101
91
|
# Public: Local variables are extracted from first frame of backtrace.
|
102
92
|
attr_reader :local_variables
|
@@ -138,24 +128,21 @@ module Honeybadger
|
|
138
128
|
@config = config
|
139
129
|
|
140
130
|
@sanitizer = Util::Sanitizer.new
|
141
|
-
@filtered_sanitizer = Util::Sanitizer.new(filters: config.params_filters)
|
142
131
|
|
143
|
-
@exception = opts[:exception]
|
132
|
+
@exception = unwrap_exception(opts[:exception])
|
144
133
|
@error_class = exception_attribute(:error_class) {|exception| exception.class.name }
|
145
|
-
@error_message =
|
146
|
-
|
147
|
-
"#{exception.class.name}: #{exception.message}"
|
148
|
-
end
|
134
|
+
@error_message = exception_attribute(:error_message, 'Notification') do |exception|
|
135
|
+
"#{exception.class.name}: #{exception.message}"
|
149
136
|
end
|
150
137
|
@backtrace = parse_backtrace(exception_attribute(:backtrace, caller))
|
151
138
|
@source = extract_source_from_backtrace(@backtrace, config, opts)
|
152
139
|
@fingerprint = construct_fingerprint(opts)
|
153
140
|
|
154
|
-
@request =
|
141
|
+
@request = construct_request_hash(config, opts)
|
155
142
|
|
156
143
|
@context = construct_context_hash(opts)
|
157
144
|
|
158
|
-
@causes = unwrap_causes(
|
145
|
+
@causes = unwrap_causes(@exception)
|
159
146
|
|
160
147
|
@tags = construct_tags(opts[:tags])
|
161
148
|
@tags = construct_tags(context[:tags]) | @tags if context
|
@@ -186,16 +173,10 @@ module Honeybadger
|
|
186
173
|
tags: s(tags),
|
187
174
|
causes: s(causes)
|
188
175
|
},
|
189
|
-
request: {
|
190
|
-
url: sanitized_url,
|
191
|
-
component: s(component),
|
192
|
-
action: s(action),
|
193
|
-
params: f(params),
|
194
|
-
session: f(session),
|
195
|
-
cgi_data: f(cgi_data),
|
176
|
+
request: @request.update({
|
196
177
|
context: s(context),
|
197
178
|
local_variables: s(local_variables)
|
198
|
-
},
|
179
|
+
}),
|
199
180
|
server: {
|
200
181
|
project_root: s(config[:root]),
|
201
182
|
environment_name: s(config[:env]),
|
@@ -239,7 +220,7 @@ module Honeybadger
|
|
239
220
|
|
240
221
|
private
|
241
222
|
|
242
|
-
attr_reader :config, :opts, :context, :stats, :api_key, :now, :pid, :causes, :sanitizer
|
223
|
+
attr_reader :config, :opts, :context, :stats, :api_key, :now, :pid, :causes, :sanitizer
|
243
224
|
|
244
225
|
def ignore_by_origin?
|
245
226
|
opts[:origin] == :rake && !config[:'exceptions.rescue_rake']
|
@@ -262,7 +243,7 @@ module Honeybadger
|
|
262
243
|
#
|
263
244
|
# Returns attribute value from args or exception, otherwise default
|
264
245
|
def exception_attribute(attribute, default = nil, &block)
|
265
|
-
opts[attribute] || (
|
246
|
+
opts[attribute] || (exception && from_exception(attribute, &block)) || default
|
266
247
|
end
|
267
248
|
|
268
249
|
# Gets a property named +attribute+ from an exception.
|
@@ -274,12 +255,12 @@ module Honeybadger
|
|
274
255
|
# If no block is given, a method with the same name as +attribute+ will be
|
275
256
|
# invoked for the value.
|
276
257
|
def from_exception(attribute)
|
277
|
-
return unless
|
258
|
+
return unless exception
|
278
259
|
|
279
260
|
if block_given?
|
280
|
-
yield(
|
261
|
+
yield(exception)
|
281
262
|
else
|
282
|
-
|
263
|
+
exception.send(attribute)
|
283
264
|
end
|
284
265
|
end
|
285
266
|
|
@@ -302,50 +283,24 @@ module Honeybadger
|
|
302
283
|
ignored_class ? @ignore_by_class.call(ignored_class) : config[:'exceptions.ignore'].any?(&@ignore_by_class)
|
303
284
|
end
|
304
285
|
|
305
|
-
# Limit size of string to bytes
|
306
|
-
#
|
307
|
-
# input - The String to be trimmed.
|
308
|
-
# bytes - The Integer bytes to trim.
|
309
|
-
# block - An optional block used in place of input.
|
310
|
-
#
|
311
|
-
# Examples
|
312
|
-
#
|
313
|
-
# trimmed = trim_size("Honeybadger doesn't care", 3)
|
314
|
-
#
|
315
|
-
# trimmed = trim_size(3) do
|
316
|
-
# "Honeybadger doesn't care"
|
317
|
-
# end
|
318
|
-
#
|
319
|
-
# Returns trimmed String
|
320
|
-
def trim_size(*args, &block)
|
321
|
-
input, bytes = args.first, args.last
|
322
|
-
input = yield if block_given?
|
323
|
-
input = input.dup
|
324
|
-
input = input[0...bytes] if input.respond_to?(:size) && input.size > bytes
|
325
|
-
input
|
326
|
-
end
|
327
|
-
|
328
286
|
def construct_backtrace_filters(opts)
|
329
287
|
[
|
330
288
|
opts[:callbacks] ? opts[:callbacks].backtrace_filter : nil
|
331
289
|
].compact | BACKTRACE_FILTERS
|
332
290
|
end
|
333
291
|
|
334
|
-
|
292
|
+
# Internal: Construct the request object with data from various sources.
|
293
|
+
#
|
294
|
+
# Returns Request.
|
295
|
+
def construct_request_hash(config, opts)
|
335
296
|
request = {}
|
336
|
-
request.merge!(
|
337
|
-
|
297
|
+
request.merge!(config.request_hash)
|
298
|
+
request.merge!(opts)
|
338
299
|
request[:component] = opts[:controller] if opts.has_key?(:controller)
|
339
300
|
request[:params] = opts[:parameters] if opts.has_key?(:parameters)
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
request[key] = default if !request[key] || excluded_keys.include?(key)
|
344
|
-
end
|
345
|
-
|
346
|
-
request[:session] = request[:session][:data] if request[:session][:data]
|
347
|
-
|
348
|
-
request
|
301
|
+
request.delete_if {|k,v| config.excluded_request_keys.include?(k) }
|
302
|
+
request[:sanitizer] = Util::Sanitizer.new(filters: config.params_filters)
|
303
|
+
Util::RequestPayload.build(request)
|
349
304
|
end
|
350
305
|
|
351
306
|
def construct_context_hash(opts)
|
@@ -356,21 +311,19 @@ module Honeybadger
|
|
356
311
|
end
|
357
312
|
|
358
313
|
def extract_source_from_backtrace(backtrace, config, opts)
|
359
|
-
if backtrace.lines.empty?
|
360
|
-
|
314
|
+
return nil if backtrace.lines.empty?
|
315
|
+
|
316
|
+
# ActionView::Template::Error has its own source_extract method.
|
317
|
+
# If present, use that instead.
|
318
|
+
if exception.respond_to?(:source_extract) && exception.source_extract
|
319
|
+
Hash[exception.source_extract.split("\n").map do |line|
|
320
|
+
parts = line.split(': ')
|
321
|
+
[parts[0].strip, parts[1] || '']
|
322
|
+
end]
|
323
|
+
elsif backtrace.application_lines.any?
|
324
|
+
backtrace.application_lines.first.source(config[:'exceptions.source_radius'])
|
361
325
|
else
|
362
|
-
|
363
|
-
# If present, use that instead.
|
364
|
-
if opts[:exception].respond_to?(:source_extract) && opts[:exception].source_extract
|
365
|
-
Hash[exception.source_extract.split("\n").map do |line|
|
366
|
-
parts = line.split(': ')
|
367
|
-
[parts[0].strip, parts[1] || '']
|
368
|
-
end]
|
369
|
-
elsif backtrace.application_lines.any?
|
370
|
-
backtrace.application_lines.first.source(config[:'exceptions.source_radius'])
|
371
|
-
else
|
372
|
-
backtrace.lines.first.source(config[:'exceptions.source_radius'])
|
373
|
-
end
|
326
|
+
backtrace.lines.first.source(config[:'exceptions.source_radius'])
|
374
327
|
end
|
375
328
|
end
|
376
329
|
|
@@ -408,15 +361,6 @@ module Honeybadger
|
|
408
361
|
sanitizer.sanitize(data)
|
409
362
|
end
|
410
363
|
|
411
|
-
def f(data)
|
412
|
-
filtered_sanitizer.sanitize(data)
|
413
|
-
end
|
414
|
-
|
415
|
-
def sanitized_url
|
416
|
-
return nil unless url
|
417
|
-
filtered_sanitizer.filter_url(s(url))
|
418
|
-
end
|
419
|
-
|
420
364
|
# Internal: Fetch local variables from first frame of backtrace.
|
421
365
|
#
|
422
366
|
# exception - The Exception containing the bindings stack.
|
@@ -458,6 +402,17 @@ module Honeybadger
|
|
458
402
|
)
|
459
403
|
end
|
460
404
|
|
405
|
+
# Internal: Unwrap the exception so that original exception is ignored or
|
406
|
+
# reported.
|
407
|
+
#
|
408
|
+
# exception - The exception which was rescued.
|
409
|
+
#
|
410
|
+
# Returns the Exception to report.
|
411
|
+
def unwrap_exception(exception)
|
412
|
+
return exception unless config[:'exceptions.unwrap']
|
413
|
+
exception_cause(exception) || exception
|
414
|
+
end
|
415
|
+
|
461
416
|
# Internal: Fetch cause from exception.
|
462
417
|
#
|
463
418
|
# exception - Exception to fetch cause from.
|
@@ -484,7 +439,7 @@ module Honeybadger
|
|
484
439
|
while (e = exception_cause(e)) && i < MAX_EXCEPTION_CAUSES
|
485
440
|
c << {
|
486
441
|
class: e.class.name,
|
487
|
-
message:
|
442
|
+
message: e.message,
|
488
443
|
backtrace: parse_backtrace(e.backtrace || caller)
|
489
444
|
}
|
490
445
|
i += 1
|
@@ -8,21 +8,34 @@ module Honeybadger
|
|
8
8
|
callbacks do |lifecycle|
|
9
9
|
lifecycle.around(:invoke_job) do |job, &block|
|
10
10
|
begin
|
11
|
+
begin #buildin suport for Delayed::PerformableMethod
|
12
|
+
component = job.payload_object.object.is_a?(Class) ? job.payload_object.object.name : job.payload_object.object.class.name
|
13
|
+
action = job.payload_object.method_name.to_s
|
14
|
+
rescue #fallback to support all other classes
|
15
|
+
component = job.payload_object.class.name
|
16
|
+
action = 'perform'
|
17
|
+
end
|
18
|
+
|
19
|
+
::Honeybadger.context(
|
20
|
+
:component => component,
|
21
|
+
:action => action,
|
22
|
+
:job_id => job.id,
|
23
|
+
:handler => job.handler,
|
24
|
+
:last_error => job.last_error,
|
25
|
+
:attempts => job.attempts,
|
26
|
+
:queue => job.queue
|
27
|
+
)
|
28
|
+
|
11
29
|
::Honeybadger::Trace.instrument("#{job.payload_object.class}#perform", {source: 'delayed_job', jid: job.id, class: job.payload_object.class.name}) do
|
12
30
|
block.call(job)
|
13
31
|
end
|
14
32
|
rescue Exception => error
|
15
33
|
::Honeybadger.notify_or_ignore(
|
34
|
+
:component => component,
|
35
|
+
:action => action,
|
16
36
|
:error_class => error.class.name,
|
17
37
|
:error_message => "#{ error.class.name }: #{ error.message }",
|
18
|
-
:backtrace => error.backtrace
|
19
|
-
:context => {
|
20
|
-
:job_id => job.id,
|
21
|
-
:handler => job.handler,
|
22
|
-
:last_error => job.last_error,
|
23
|
-
:attempts => job.attempts,
|
24
|
-
:queue => job.queue
|
25
|
-
}
|
38
|
+
:backtrace => error.backtrace
|
26
39
|
) if job.attempts.to_i >= ::Honeybadger::Agent.config[:'delayed_job.attempt_threshold'].to_i
|
27
40
|
raise error
|
28
41
|
ensure
|