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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/Appraisals +56 -46
  3. data/CHANGELOG.md +33 -0
  4. data/Gemfile.lock +1 -1
  5. data/MIT-LICENSE +2 -1
  6. data/Rakefile +3 -1
  7. data/features/standalone.feature +73 -0
  8. data/features/step_definitions/rack_steps.rb +1 -2
  9. data/features/step_definitions/standalone_steps.rb +12 -0
  10. data/features/support/env.rb +2 -0
  11. data/gemfiles/binding_of_caller.gemfile +8 -0
  12. data/gemfiles/rails.gemfile +11 -0
  13. data/gemfiles/standalone.gemfile +7 -0
  14. data/honeybadger.gemspec +22 -11
  15. data/lib/honeybadger.rb +15 -9
  16. data/lib/honeybadger/configuration.rb +9 -4
  17. data/lib/honeybadger/dependency.rb +65 -0
  18. data/lib/honeybadger/exception_extensions.rb +35 -0
  19. data/lib/honeybadger/integrations.rb +4 -0
  20. data/lib/honeybadger/integrations/delayed_job.rb +20 -0
  21. data/lib/honeybadger/integrations/delayed_job/plugin.rb +31 -0
  22. data/lib/honeybadger/integrations/sidekiq.rb +34 -0
  23. data/lib/honeybadger/notice.rb +48 -11
  24. data/lib/honeybadger/payload.rb +29 -0
  25. data/lib/honeybadger/rack.rb +8 -54
  26. data/lib/honeybadger/rack/error_notifier.rb +60 -0
  27. data/lib/honeybadger/rack/user_feedback.rb +74 -0
  28. data/lib/honeybadger/rack/user_informer.rb +28 -0
  29. data/lib/honeybadger/rails.rb +5 -4
  30. data/lib/honeybadger/railtie.rb +4 -3
  31. data/lib/honeybadger/user_feedback.rb +3 -67
  32. data/lib/honeybadger/user_informer.rb +3 -21
  33. data/spec/honeybadger/configuration_spec.rb +5 -1
  34. data/spec/honeybadger/dependency_spec.rb +134 -0
  35. data/spec/honeybadger/exception_extensions_spec.rb +40 -0
  36. data/spec/honeybadger/integrations/delayed_job_spec.rb +48 -0
  37. data/spec/honeybadger/integrations/sidekiq_spec.rb +60 -0
  38. data/spec/honeybadger/notice_spec.rb +176 -35
  39. data/spec/honeybadger/payload_spec.rb +27 -0
  40. data/spec/honeybadger/rails_spec.rb +4 -2
  41. metadata +24 -13
  42. data/gemfiles/rack.gemfile.lock +0 -125
  43. data/gemfiles/rails2.3.gemfile.lock +0 -141
  44. data/gemfiles/rails3.0.gemfile.lock +0 -193
  45. data/gemfiles/rails3.1.gemfile.lock +0 -203
  46. data/gemfiles/rails3.2.gemfile.lock +0 -201
  47. data/gemfiles/rails4.0.gemfile.lock +0 -197
  48. data/gemfiles/rails4.1.gemfile.lock +0 -202
  49. data/gemfiles/rake.gemfile.lock +0 -124
  50. 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.11.2'
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.deliver
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
- if exception.respond_to?(:original_exception)
182
- exception.original_exception
183
- elsif exception.respond_to?(:continued_exception)
184
- exception.continued_exception
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, :metrics, :log_exception_on_send_failure].freeze
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 = { 'notices' => true }
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.deliver } }
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.deliver }
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,4 @@
1
+ module Honeybadger::Integrations; end
2
+
3
+ require 'honeybadger/integrations/delayed_job'
4
+ require 'honeybadger/integrations/sidekiq'
@@ -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
@@ -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.send_request_session = args[:send_request_session].nil? ? true : args[:send_request_session]
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
- # Public: Send the notice to Honeybadger using the configured sender
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, :context,
239
- :source_extract, :source_extract_radius, :send_request_session, :api_key
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