honeybadger 1.11.2 → 1.12.0.beta2
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 +4 -4
- data/Appraisals +56 -46
- data/CHANGELOG.md +33 -0
- data/Gemfile.lock +1 -1
- data/MIT-LICENSE +2 -1
- data/Rakefile +3 -1
- data/features/standalone.feature +73 -0
- data/features/step_definitions/rack_steps.rb +1 -2
- data/features/step_definitions/standalone_steps.rb +12 -0
- data/features/support/env.rb +2 -0
- data/gemfiles/binding_of_caller.gemfile +8 -0
- data/gemfiles/rails.gemfile +11 -0
- data/gemfiles/standalone.gemfile +7 -0
- data/honeybadger.gemspec +22 -11
- data/lib/honeybadger.rb +15 -9
- data/lib/honeybadger/configuration.rb +9 -4
- data/lib/honeybadger/dependency.rb +65 -0
- data/lib/honeybadger/exception_extensions.rb +35 -0
- data/lib/honeybadger/integrations.rb +4 -0
- data/lib/honeybadger/integrations/delayed_job.rb +20 -0
- data/lib/honeybadger/integrations/delayed_job/plugin.rb +31 -0
- data/lib/honeybadger/integrations/sidekiq.rb +34 -0
- data/lib/honeybadger/notice.rb +48 -11
- data/lib/honeybadger/payload.rb +29 -0
- data/lib/honeybadger/rack.rb +8 -54
- data/lib/honeybadger/rack/error_notifier.rb +60 -0
- data/lib/honeybadger/rack/user_feedback.rb +74 -0
- data/lib/honeybadger/rack/user_informer.rb +28 -0
- data/lib/honeybadger/rails.rb +5 -4
- data/lib/honeybadger/railtie.rb +4 -3
- data/lib/honeybadger/user_feedback.rb +3 -67
- data/lib/honeybadger/user_informer.rb +3 -21
- data/spec/honeybadger/configuration_spec.rb +5 -1
- data/spec/honeybadger/dependency_spec.rb +134 -0
- data/spec/honeybadger/exception_extensions_spec.rb +40 -0
- data/spec/honeybadger/integrations/delayed_job_spec.rb +48 -0
- data/spec/honeybadger/integrations/sidekiq_spec.rb +60 -0
- data/spec/honeybadger/notice_spec.rb +176 -35
- data/spec/honeybadger/payload_spec.rb +27 -0
- data/spec/honeybadger/rails_spec.rb +4 -2
- metadata +24 -13
- data/gemfiles/rack.gemfile.lock +0 -125
- data/gemfiles/rails2.3.gemfile.lock +0 -141
- data/gemfiles/rails3.0.gemfile.lock +0 -193
- data/gemfiles/rails3.1.gemfile.lock +0 -203
- data/gemfiles/rails3.2.gemfile.lock +0 -201
- data/gemfiles/rails4.0.gemfile.lock +0 -197
- data/gemfiles/rails4.1.gemfile.lock +0 -202
- data/gemfiles/rake.gemfile.lock +0 -124
- data/gemfiles/sinatra.gemfile.lock +0 -124
data/lib/honeybadger.rb
CHANGED
@@ -4,6 +4,7 @@ require 'json'
|
|
4
4
|
require 'digest'
|
5
5
|
require 'logger'
|
6
6
|
|
7
|
+
require 'honeybadger/dependency'
|
7
8
|
require 'honeybadger/configuration'
|
8
9
|
require 'honeybadger/backtrace'
|
9
10
|
require 'honeybadger/notice'
|
@@ -12,11 +13,13 @@ require 'honeybadger/sender'
|
|
12
13
|
require 'honeybadger/stats'
|
13
14
|
require 'honeybadger/user_informer'
|
14
15
|
require 'honeybadger/user_feedback'
|
16
|
+
require 'honeybadger/integrations'
|
17
|
+
require 'honeybadger/exception_extensions'
|
15
18
|
|
16
19
|
require 'honeybadger/railtie' if defined?(Rails::Railtie)
|
17
20
|
|
18
21
|
module Honeybadger
|
19
|
-
VERSION = '1.
|
22
|
+
VERSION = '1.12.0.beta2'
|
20
23
|
LOG_PREFIX = "** [Honeybadger] "
|
21
24
|
|
22
25
|
HEADERS = {
|
@@ -161,11 +164,13 @@ module Honeybadger
|
|
161
164
|
private
|
162
165
|
|
163
166
|
def send_notice(notice)
|
167
|
+
return false unless sender
|
168
|
+
|
164
169
|
if configuration.public?
|
165
170
|
if configuration.async?
|
166
171
|
configuration.async.call(notice)
|
167
172
|
else
|
168
|
-
notice
|
173
|
+
Honeybadger.sender.send_to_honeybadger(notice)
|
169
174
|
end
|
170
175
|
end
|
171
176
|
end
|
@@ -178,13 +183,14 @@ module Honeybadger
|
|
178
183
|
end
|
179
184
|
|
180
185
|
def unwrap_exception(exception)
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
else
|
186
|
-
exception
|
187
|
-
end
|
186
|
+
exception.respond_to?(:original_exception) && exception.original_exception ||
|
187
|
+
exception.respond_to?(:continued_exception) && exception.continued_exception ||
|
188
|
+
exception.respond_to?(:cause) && exception.cause ||
|
189
|
+
exception
|
188
190
|
end
|
189
191
|
end
|
190
192
|
end
|
193
|
+
|
194
|
+
unless defined?(Rails)
|
195
|
+
Honeybadger::Dependency.inject!
|
196
|
+
end
|
@@ -8,7 +8,8 @@ module Honeybadger
|
|
8
8
|
:params_filters, :project_root, :port, :protocol, :proxy_host, :proxy_pass,
|
9
9
|
:proxy_port, :proxy_user, :secure, :use_system_ssl_cert_chain, :framework,
|
10
10
|
:user_information, :feedback, :rescue_rake_exceptions, :source_extract_radius,
|
11
|
-
:send_request_session, :debug, :fingerprint, :hostname, :
|
11
|
+
:send_request_session, :debug, :fingerprint, :hostname, :features, :metrics,
|
12
|
+
:log_exception_on_send_failure, :send_local_variables].freeze
|
12
13
|
|
13
14
|
# The API key for your project, found on the project edit form.
|
14
15
|
attr_accessor :api_key
|
@@ -100,6 +101,9 @@ module Honeybadger
|
|
100
101
|
# +true+ to send session data, +false+ to exclude
|
101
102
|
attr_accessor :send_request_session
|
102
103
|
|
104
|
+
# +true+ to send local variables, +false+ to exclude
|
105
|
+
attr_accessor :send_local_variables
|
106
|
+
|
103
107
|
# +true+ to log extra debug info, +false+ to suppress
|
104
108
|
attr_accessor :debug
|
105
109
|
|
@@ -175,11 +179,12 @@ module Honeybadger
|
|
175
179
|
@rescue_rake_exceptions = nil
|
176
180
|
@source_extract_radius = 2
|
177
181
|
@send_request_session = true
|
182
|
+
@send_local_variables = false
|
178
183
|
@debug = false
|
179
184
|
@log_exception_on_send_failure = false
|
180
185
|
@hostname = Socket.gethostname
|
181
186
|
@metrics = true
|
182
|
-
@features = {
|
187
|
+
@features = {'notices' => true, 'local_variables' => true}
|
183
188
|
@limit = nil
|
184
189
|
@feedback = true
|
185
190
|
end
|
@@ -283,10 +288,10 @@ module Honeybadger
|
|
283
288
|
#
|
284
289
|
# Examples
|
285
290
|
#
|
286
|
-
# config.async = Proc.new { |notice| Thread.new { notice
|
291
|
+
# config.async = Proc.new { |notice| Thread.new { Honeybadger.sender.send_to_honeybadger(notice) } }
|
287
292
|
#
|
288
293
|
# config.async do |notice|
|
289
|
-
# Thread.new { notice
|
294
|
+
# Thread.new { Honeybadger.sender.send_to_honeybadger(notice) }
|
290
295
|
# end
|
291
296
|
#
|
292
297
|
# Returns configured async handler (should respond to #call(notice))
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Honeybadger
|
2
|
+
class Dependency
|
3
|
+
class << self
|
4
|
+
@@instances = []
|
5
|
+
|
6
|
+
def instances
|
7
|
+
@@instances
|
8
|
+
end
|
9
|
+
|
10
|
+
def register
|
11
|
+
instances << new.tap { |d| d.instance_eval(&Proc.new) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def inject!
|
15
|
+
instances.each do |dependency|
|
16
|
+
dependency.inject! if dependency.ok?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def reset!
|
21
|
+
instances.each(&:reset!)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
@injected = false
|
27
|
+
@requirements = []
|
28
|
+
@injections = []
|
29
|
+
end
|
30
|
+
|
31
|
+
def requirement
|
32
|
+
@requirements << Proc.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def injection
|
36
|
+
@injections << Proc.new
|
37
|
+
end
|
38
|
+
|
39
|
+
def ok?
|
40
|
+
!@injected && @requirements.all?(&:call)
|
41
|
+
rescue => e
|
42
|
+
Honeybadger.write_verbose_log("Exception caught while verifying dependency: #{e.class} -- #{e.message}", :error)
|
43
|
+
false
|
44
|
+
end
|
45
|
+
|
46
|
+
def inject!
|
47
|
+
@injections.each(&:call)
|
48
|
+
rescue => e
|
49
|
+
Honeybadger.write_verbose_log("Exception caught while injecting dependency: #{e.class} -- #{e.message}", :error)
|
50
|
+
false
|
51
|
+
ensure
|
52
|
+
@injected = true
|
53
|
+
end
|
54
|
+
|
55
|
+
def reset!
|
56
|
+
@injected = false
|
57
|
+
end
|
58
|
+
|
59
|
+
def injected?
|
60
|
+
@injected
|
61
|
+
end
|
62
|
+
|
63
|
+
attr_reader :requirements, :injections
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Honeybadger
|
2
|
+
module ExceptionExtensions
|
3
|
+
module Bindings
|
4
|
+
def self.included(base)
|
5
|
+
base.send(:alias_method, :set_backtrace_without_honeybadger, :set_backtrace)
|
6
|
+
base.send(:alias_method, :set_backtrace, :set_backtrace_with_honeybadger)
|
7
|
+
end
|
8
|
+
|
9
|
+
def set_backtrace_with_honeybadger(*args, &block)
|
10
|
+
if caller.none? { |loc| loc.match(Honeybadger::Backtrace::Line::INPUT_FORMAT) && Regexp.last_match(1) == __FILE__ }
|
11
|
+
@__honeybadger_bindings_stack = binding.callers.drop(1)
|
12
|
+
end
|
13
|
+
|
14
|
+
set_backtrace_without_honeybadger(*args, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
def __honeybadger_bindings_stack
|
18
|
+
@__honeybadger_bindings_stack || []
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module NullBindings
|
23
|
+
def __honeybadger_bindings_stack
|
24
|
+
[]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
begin
|
31
|
+
require 'binding_of_caller'
|
32
|
+
Exception.send(:include, Honeybadger::ExceptionExtensions::Bindings)
|
33
|
+
rescue LoadError
|
34
|
+
Exception.send(:include, Honeybadger::ExceptionExtensions::NullBindings)
|
35
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Honeybadger
|
2
|
+
Dependency.register do
|
3
|
+
requirement { defined?(::Delayed::Plugins::Plugin) }
|
4
|
+
requirement { defined?(::Delayed::Worker.plugins) }
|
5
|
+
requirement do
|
6
|
+
if delayed_job_honeybadger = defined?(::Delayed::Plugins::Honeybadger)
|
7
|
+
Honeybadger.write_verbose_log("Support for Delayed Job has been moved " \
|
8
|
+
"to the honeybadger gem. Please remove " \
|
9
|
+
"delayed_job_honeybadger from your " \
|
10
|
+
"Gemfile.", :warn)
|
11
|
+
end
|
12
|
+
!delayed_job_honeybadger
|
13
|
+
end
|
14
|
+
|
15
|
+
injection do
|
16
|
+
require 'honeybadger/integrations/delayed_job/plugin'
|
17
|
+
::Delayed::Worker.plugins << Integrations::DelayedJob::Plugin
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Honeybadger
|
2
|
+
module Integrations
|
3
|
+
module DelayedJob
|
4
|
+
class Plugin < ::Delayed::Plugins::Plugin
|
5
|
+
callbacks do |lifecycle|
|
6
|
+
lifecycle.around(:invoke_job) do |job, *args, &block|
|
7
|
+
begin
|
8
|
+
block.call(job, *args)
|
9
|
+
rescue Exception => error
|
10
|
+
::Honeybadger.notify_or_ignore(
|
11
|
+
:error_class => error.class.name,
|
12
|
+
:error_message => "#{ error.class.name }: #{ error.message }",
|
13
|
+
:backtrace => error.backtrace,
|
14
|
+
:context => {
|
15
|
+
:job_id => job.id,
|
16
|
+
:handler => job.handler,
|
17
|
+
:last_error => job.last_error,
|
18
|
+
:attempts => job.attempts,
|
19
|
+
:queue => job.queue
|
20
|
+
}
|
21
|
+
)
|
22
|
+
raise error
|
23
|
+
ensure
|
24
|
+
::Honeybadger.context.clear!
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Honeybadger
|
2
|
+
module Integrations
|
3
|
+
module Sidekiq
|
4
|
+
class Middleware
|
5
|
+
def call(worker, msg, queue)
|
6
|
+
Honeybadger.context.clear!
|
7
|
+
yield
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
Dependency.register do
|
14
|
+
requirement { defined?(::Sidekiq) }
|
15
|
+
|
16
|
+
injection do
|
17
|
+
::Sidekiq.configure_server do |config|
|
18
|
+
config.server_middleware do |chain|
|
19
|
+
chain.add Integrations::Sidekiq::Middleware
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
Dependency.register do
|
26
|
+
requirement { defined?(::Sidekiq::VERSION) && ::Sidekiq::VERSION > '3' }
|
27
|
+
|
28
|
+
injection do
|
29
|
+
::Sidekiq.configure_server do |config|
|
30
|
+
config.error_handlers << Proc.new {|ex,context| Honeybadger.notify_or_ignore(ex, :parameters => context) }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/honeybadger/notice.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'honeybadger/payload'
|
1
2
|
require 'socket'
|
2
3
|
|
3
4
|
module Honeybadger
|
@@ -85,8 +86,15 @@ module Honeybadger
|
|
85
86
|
# The api_key to use when sending notice (optional)
|
86
87
|
attr_reader :api_key
|
87
88
|
|
89
|
+
# Local variables are extracted from first frame of backtrace
|
90
|
+
attr_reader :local_variables
|
91
|
+
|
92
|
+
# Additional features to enable/disable
|
93
|
+
attr_reader :features
|
94
|
+
|
88
95
|
def initialize(args)
|
89
96
|
self.args = args
|
97
|
+
self.features = args[:features] || {}
|
90
98
|
self.exception = args[:exception]
|
91
99
|
self.project_root = args[:project_root]
|
92
100
|
|
@@ -124,7 +132,9 @@ module Honeybadger
|
|
124
132
|
self.source_extract_radius = args[:source_extract_radius] || 2
|
125
133
|
self.source_extract = extract_source_from_backtrace
|
126
134
|
|
127
|
-
self.
|
135
|
+
self.local_variables = send_local_variables? ? local_variables_from_exception(exception) : {}
|
136
|
+
|
137
|
+
self.send_request_session = args[:send_request_session].nil? ? true : args[:send_request_session]
|
128
138
|
|
129
139
|
also_use_rack_params_filters
|
130
140
|
find_session_data
|
@@ -133,10 +143,7 @@ module Honeybadger
|
|
133
143
|
set_context
|
134
144
|
end
|
135
145
|
|
136
|
-
#
|
137
|
-
#
|
138
|
-
# Returns a reference to the error in Honeybadger, false if sender isn't
|
139
|
-
# configured
|
146
|
+
# Deprecated. Remove in 2.0.
|
140
147
|
def deliver
|
141
148
|
return false unless Honeybadger.sender
|
142
149
|
Honeybadger.sender.send_to_honeybadger(self)
|
@@ -146,7 +153,7 @@ module Honeybadger
|
|
146
153
|
#
|
147
154
|
# Returns JSON representation of notice
|
148
155
|
def as_json(options = {})
|
149
|
-
{
|
156
|
+
Payload.new({
|
150
157
|
:api_key => api_key,
|
151
158
|
:notifier => {
|
152
159
|
:name => notifier_name,
|
@@ -168,7 +175,8 @@ module Honeybadger
|
|
168
175
|
:params => parameters,
|
169
176
|
:session => session_data,
|
170
177
|
:cgi_data => cgi_data,
|
171
|
-
:context => context
|
178
|
+
:context => context,
|
179
|
+
:local_variables => local_variables
|
172
180
|
},
|
173
181
|
:server => {
|
174
182
|
:project_root => project_root,
|
@@ -176,7 +184,7 @@ module Honeybadger
|
|
176
184
|
:hostname => hostname,
|
177
185
|
:stats => stats
|
178
186
|
}
|
179
|
-
}
|
187
|
+
})
|
180
188
|
end
|
181
189
|
|
182
190
|
# Public: Creates JSON
|
@@ -235,8 +243,9 @@ module Honeybadger
|
|
235
243
|
:error_message, :backtrace_filters, :parameters, :params_filters,
|
236
244
|
:environment_filters, :session_data, :project_root, :url, :ignore,
|
237
245
|
:ignore_by_filters, :notifier_name, :notifier_url, :notifier_version,
|
238
|
-
:component, :action, :cgi_data, :environment_name, :hostname, :stats,
|
239
|
-
:source_extract, :source_extract_radius, :send_request_session,
|
246
|
+
:component, :action, :cgi_data, :environment_name, :hostname, :stats,
|
247
|
+
:context, :source_extract, :source_extract_radius, :send_request_session,
|
248
|
+
:api_key, :features, :local_variables
|
240
249
|
|
241
250
|
# Private: Arguments given in the initializer
|
242
251
|
attr_accessor :args
|
@@ -331,7 +340,7 @@ module Honeybadger
|
|
331
340
|
|
332
341
|
def filter_cgi_data_params(cgi_data)
|
333
342
|
cgi_data.each_pair do |key, value|
|
334
|
-
next unless value.kind_of?(String) && key =~ /\A[A-Z_]+\Z/
|
343
|
+
next unless value.kind_of?(String) && key =~ /\A[A-Z_]+\Z/ && value =~ /\S/
|
335
344
|
cgi_data[key] = filter_url(value)
|
336
345
|
end
|
337
346
|
end
|
@@ -340,6 +349,8 @@ module Honeybadger
|
|
340
349
|
if cgi_data
|
341
350
|
cgi_data.delete("rack.request.form_vars")
|
342
351
|
cgi_data.delete("rack.request.query_string")
|
352
|
+
cgi_data.delete("action_dispatch.request.parameters")
|
353
|
+
cgi_data.delete("action_dispatch.request.request_parameters")
|
343
354
|
end
|
344
355
|
end
|
345
356
|
|
@@ -468,5 +479,31 @@ module Honeybadger
|
|
468
479
|
input = input[0...bytes] if input.respond_to?(:size) && input.size > bytes
|
469
480
|
input
|
470
481
|
end
|
482
|
+
|
483
|
+
# Internal: Fetch local variables from first frame of backtrace.
|
484
|
+
#
|
485
|
+
# exception - The Exception containing the bindings stack.
|
486
|
+
#
|
487
|
+
# Returns a Hash of local variables
|
488
|
+
def local_variables_from_exception(exception)
|
489
|
+
return {} unless Exception === exception
|
490
|
+
return {} if exception.__honeybadger_bindings_stack.empty?
|
491
|
+
|
492
|
+
if project_root
|
493
|
+
binding = exception.__honeybadger_bindings_stack.find { |b| b.eval('__FILE__') =~ /^#{Regexp.escape(project_root.to_s)}/ }
|
494
|
+
end
|
495
|
+
|
496
|
+
binding ||= exception.__honeybadger_bindings_stack[0]
|
497
|
+
|
498
|
+
vars = binding.eval('local_variables')
|
499
|
+
Hash[vars.map {|arg| [arg, binding.eval(arg.to_s)]}]
|
500
|
+
end
|
501
|
+
|
502
|
+
# Internal: Should local variables be sent?
|
503
|
+
#
|
504
|
+
# Returns true to send local_variables
|
505
|
+
def send_local_variables?
|
506
|
+
args[:send_local_variables] && features['local_variables']
|
507
|
+
end
|
471
508
|
end
|
472
509
|
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
module Honeybadger
|
4
|
+
class Payload < SimpleDelegator
|
5
|
+
attr_reader :max_depth
|
6
|
+
|
7
|
+
def initialize(hash = {}, options = {})
|
8
|
+
fail ArgumentError, 'must be a Hash' unless hash.kind_of?(Hash)
|
9
|
+
@max_depth = options[:max_depth] || 20
|
10
|
+
super(hash)
|
11
|
+
sanitize(self)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def sanitize(hash, depth = 0)
|
17
|
+
hash.each_pair do |k,v|
|
18
|
+
next unless v.kind_of?(Hash)
|
19
|
+
if depth >= max_depth
|
20
|
+
hash.delete(k)
|
21
|
+
else
|
22
|
+
hash[k] = sanitize(v, depth+1)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
hash
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|