honeybadger 3.1.2 → 3.2.0.beta1

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.
@@ -1,5 +1,8 @@
1
+ require 'honeybadger/conversions'
2
+
1
3
  module Honeybadger
2
4
  class ContextManager
5
+ include Conversions
3
6
 
4
7
  def self.current
5
8
  Thread.current[:__hb_context_manager] ||= new
@@ -19,7 +22,7 @@ module Honeybadger
19
22
  def set_context(hash)
20
23
  @mutex.synchronize do
21
24
  @context ||= {}
22
- @context.update(hash)
25
+ @context.update(Context(hash))
23
26
  end
24
27
  end
25
28
 
@@ -0,0 +1,15 @@
1
+ module Honeybadger
2
+ module Conversions
3
+ module_function
4
+
5
+ # Internal: Convert context into a Hash.
6
+ #
7
+ # object - The context object.
8
+ #
9
+ # Returns the Hash context.
10
+ def Context(object)
11
+ object = object.to_honeybadger_context if object.respond_to?(:to_honeybadger_context)
12
+ Hash(object)
13
+ end
14
+ end
15
+ end
@@ -85,7 +85,7 @@ module Honeybadger
85
85
  class StandardLogger < Base
86
86
  extend Forwardable
87
87
 
88
- def initialize(logger = Logger.new('/dev/null'))
88
+ def initialize(logger = Logger.new(nil))
89
89
  raise ArgumentError, 'logger not specified' unless logger
90
90
  raise ArgumentError, 'logger must be a logger' unless logger.respond_to?(:add)
91
91
 
@@ -115,7 +115,7 @@ module Honeybadger
115
115
  INFO_SUPPLEMENT = ' level=%s pid=%s'.freeze
116
116
  DEBUG_SUPPLEMENT = ' at=%s'.freeze
117
117
 
118
- def initialize(config, logger = Logger.new('/dev/null'))
118
+ def initialize(config, logger = Logger.new(nil))
119
119
  @config = config
120
120
  @tty = STDOUT.tty?
121
121
  @tty_level = @config.log_level(:'logging.tty_level')
@@ -4,6 +4,7 @@ require 'forwardable'
4
4
 
5
5
  require 'honeybadger/version'
6
6
  require 'honeybadger/backtrace'
7
+ require 'honeybadger/conversions'
7
8
  require 'honeybadger/util/stats'
8
9
  require 'honeybadger/util/sanitizer'
9
10
  require 'honeybadger/util/request_hash'
@@ -23,7 +24,7 @@ module Honeybadger
23
24
  # Internal: Substitution for project root in backtrace lines.
24
25
  PROJECT_ROOT = '[PROJECT_ROOT]'.freeze
25
26
 
26
- # Internal: Empty String (used for equality comparisons and assignment)
27
+ # Internal: Empty String (used for equality comparisons and assignment).
27
28
  STRING_EMPTY = ''.freeze
28
29
 
29
30
  # Internal: A Regexp which matches non-blank characters.
@@ -37,6 +38,8 @@ module Honeybadger
37
38
  class Notice
38
39
  extend Forwardable
39
40
 
41
+ include Conversions
42
+
40
43
  # Internal: The String character used to split tag strings.
41
44
  TAG_SEPERATOR = ','.freeze
42
45
 
@@ -50,6 +53,9 @@ module Honeybadger
50
53
  # Public: The exception that caused this notice, if any.
51
54
  attr_reader :exception
52
55
 
56
+ # Public: The exception cause if available.
57
+ attr_reader :cause
58
+
53
59
  # Public: The backtrace from the given exception or hash.
54
60
  attr_reader :backtrace
55
61
 
@@ -59,7 +65,7 @@ module Honeybadger
59
65
  # Public: Tags which will be applied to error.
60
66
  attr_reader :tags
61
67
 
62
- # Public: The name of the class of error. (example: RuntimeError)
68
+ # Public: The name of the class of error (example: RuntimeError).
63
69
  attr_reader :error_class
64
70
 
65
71
  # Public: The message from the exception, or a general description of the error.
@@ -75,7 +81,7 @@ module Honeybadger
75
81
  def params; @request[:params]; end
76
82
  alias_method :parameters, :params
77
83
 
78
- # Public: The component (if any) which was used in this request. (usually the controller)
84
+ # Public: The component (if any) which was used in this request (usually the controller).
79
85
  def component; @request[:component]; end
80
86
  alias_method :controller, :component
81
87
 
@@ -144,9 +150,10 @@ module Honeybadger
144
150
 
145
151
  @request = construct_request_hash(config, opts)
146
152
 
147
- @context = construct_context_hash(opts)
153
+ @context = construct_context_hash(opts, exception)
148
154
 
149
- @causes = unwrap_causes(@exception)
155
+ @cause = opts[:cause] || exception_cause(@exception) || $!
156
+ @causes = unwrap_causes(@cause)
150
157
 
151
158
  @tags = construct_tags(opts[:tags])
152
159
  @tags = construct_tags(context[:tags]) | @tags if context
@@ -163,9 +170,9 @@ module Honeybadger
163
170
  @fingerprint = construct_fingerprint(opts)
164
171
  end
165
172
 
166
- # Internal: Template used to create JSON payload
173
+ # Internal: Template used to create JSON payload.
167
174
  #
168
- # Returns Hash JSON representation of notice
175
+ # Returns Hash JSON representation of notice.
169
176
  def as_json(*args)
170
177
  @request[:context] = s(context)
171
178
  @request[:local_variables] = local_variables if local_variables
@@ -195,22 +202,22 @@ module Honeybadger
195
202
  }
196
203
  end
197
204
 
198
- # Public: Creates JSON
205
+ # Public: Creates JSON.
199
206
  #
200
- # Returns valid JSON representation of Notice
207
+ # Returns valid JSON representation of Notice.
201
208
  def to_json(*a)
202
209
  ::JSON.generate(as_json(*a))
203
210
  end
204
211
 
205
- # Public: Allows properties to be accessed using a hash-like syntax
212
+ # Public: Allows properties to be accessed using a hash-like syntax.
206
213
  #
207
- # method - The given key for an attribute
214
+ # method - The given key for an attribute.
208
215
  #
209
- # Examples:
216
+ # Examples
210
217
  #
211
218
  # notice[:error_message]
212
219
  #
213
- # Returns the attribute value, or self if given +:request+
220
+ # Returns the attribute value, or self if given `:request`.
214
221
  def [](method)
215
222
  case method
216
223
  when :request
@@ -220,7 +227,7 @@ module Honeybadger
220
227
  end
221
228
  end
222
229
 
223
- # Internal: Determines if this notice should be ignored
230
+ # Internal: Determines if this notice should be ignored.
224
231
  def ignore?
225
232
  ignore_by_origin? || ignore_by_class? || ignore_by_callbacks?
226
233
  end
@@ -242,15 +249,15 @@ module Honeybadger
242
249
  end
243
250
 
244
251
  # Gets a property named "attribute" of an exception, either from
245
- # the #args hash or actual exception (in order of precidence)
252
+ # the #args hash or actual exception (in order of precidence).
246
253
  #
247
254
  # attribute - A Symbol existing as a key in #args and/or attribute on
248
- # Exception
249
- # default - Default value if no other value is found. (optional)
255
+ # Exception.
256
+ # default - Default value if no other value is found (optional).
250
257
  # block - An optional block which receives an Exception and returns the
251
- # desired value
258
+ # desired value.
252
259
  #
253
- # Returns attribute value from args or exception, otherwise default
260
+ # Returns attribute value from args or exception, otherwise default.
254
261
  def exception_attribute(attribute, default = nil, &block)
255
262
  opts[attribute] || (exception && from_exception(attribute, &block)) || default
256
263
  end
@@ -273,12 +280,12 @@ module Honeybadger
273
280
  end
274
281
  end
275
282
 
276
- # Internal: Determines if error class should be ignored
283
+ # Internal: Determines if error class should be ignored.
277
284
  #
278
285
  # ignored_class_name - The name of the ignored class. May be a
279
- # string or regexp (optional)
286
+ # string or regexp (optional).
280
287
  #
281
- # Returns true or false
288
+ # Returns true or false.
282
289
  def ignore_by_class?(ignored_class = nil)
283
290
  @ignore_by_class ||= Proc.new do |ignored_class|
284
291
  case error_class
@@ -317,10 +324,16 @@ module Honeybadger
317
324
  Util::RequestPayload.build(request)
318
325
  end
319
326
 
320
- def construct_context_hash(opts)
327
+ def exception_context(exception)
328
+ return {}.freeze unless exception.respond_to?(:to_honeybadger_context)
329
+ exception.to_honeybadger_context
330
+ end
331
+
332
+ def construct_context_hash(opts, exception)
321
333
  context = {}
322
- context.merge!(opts[:global_context]) if opts[:global_context]
323
- context.merge!(opts[:context]) if opts[:context]
334
+ context.merge!(Context(opts[:global_context]))
335
+ context.merge!(Context(exception_context(exception)))
336
+ context.merge!(Context(opts[:context]))
324
337
  context.empty? ? nil : context
325
338
  end
326
339
 
@@ -362,7 +375,7 @@ module Honeybadger
362
375
  #
363
376
  # exception - The Exception containing the bindings stack.
364
377
  #
365
- # Returns a Hash of local variables
378
+ # Returns a Hash of local variables.
366
379
  def local_variables_from_exception(exception, config)
367
380
  return nil unless send_local_variables?(config)
368
381
  return {} unless Exception === exception
@@ -394,7 +407,7 @@ module Honeybadger
394
407
 
395
408
  # Internal: Should local variables be sent?
396
409
  #
397
- # Returns true to send local_variables
410
+ # Returns true to send local_variables.
398
411
  def send_local_variables?(config)
399
412
  config[:'exceptions.local_variables']
400
413
  end
@@ -440,23 +453,25 @@ module Honeybadger
440
453
  end
441
454
  end
442
455
 
443
- # Internal: Unwrap causes from exception.
456
+ # Internal: Create a list of causes.
444
457
  #
445
- # exception - Exception to unwrap.
458
+ # cause - The first cause to unwrap.
446
459
  #
447
- # Returns Hash causes (in payload format).
448
- def unwrap_causes(exception)
449
- c, e, i = [], exception, 0
450
- while (e = exception_cause(e)) && i < MAX_EXCEPTION_CAUSES
451
- c << {
452
- class: e.class.name,
453
- message: e.message,
454
- backtrace: parse_backtrace(e.backtrace || caller).to_a
460
+ # Returns Array causes (in Hash payload format).
461
+ def unwrap_causes(cause)
462
+ causes, c, i = [], cause, 0
463
+
464
+ while c && i < MAX_EXCEPTION_CAUSES
465
+ causes << {
466
+ class: c.class.name,
467
+ message: c.message,
468
+ backtrace: parse_backtrace(c.backtrace || caller).to_a
455
469
  }
456
470
  i += 1
471
+ c = exception_cause(c)
457
472
  end
458
473
 
459
- c
474
+ causes
460
475
  end
461
476
 
462
477
  def params_filters
@@ -486,7 +501,7 @@ module Honeybadger
486
501
  # This method restores the correct #session method on @request and warns
487
502
  # the user of the issue.
488
503
  #
489
- # Returns nothing
504
+ # Returns nothing.
490
505
  def monkey_patch_action_dispatch_test_process!
491
506
  return unless defined?(ActionDispatch::TestProcess) && defined?(self.fixture_file_upload)
492
507
 
@@ -24,9 +24,10 @@ module Honeybadger
24
24
  if defined?(::Sidekiq::VERSION) && ::Sidekiq::VERSION > '3'
25
25
  ::Sidekiq.configure_server do |sidekiq|
26
26
  sidekiq.error_handlers << lambda {|ex, params|
27
- return if params['retry'.freeze] && params['retry_count'.freeze].to_i < config[:'sidekiq.attempt_threshold'].to_i
27
+ job = params[:job] || params
28
+ return if job['retry'.freeze] && job['retry_count'.freeze].to_i < config[:'sidekiq.attempt_threshold'].to_i
28
29
  opts = {parameters: params}
29
- opts[:component] = params['wrapped'.freeze] || params['class'.freeze] if config[:'sidekiq.use_component']
30
+ opts[:component] = job['wrapped'.freeze] || job['class'.freeze] if config[:'sidekiq.use_component']
30
31
  Honeybadger.notify(ex, opts)
31
32
  }
32
33
  end
@@ -8,7 +8,7 @@ module Honeybadger
8
8
  # Public: Middleware for Rack applications. Any errors raised by the upstream
9
9
  # application will be delivered to Honeybadger and re-raised.
10
10
  #
11
- # Examples:
11
+ # Examples
12
12
  #
13
13
  # require 'honeybadger/rack/error_notifier'
14
14
  #
@@ -57,7 +57,7 @@ module Honeybadger
57
57
  end
58
58
 
59
59
  def custom_template_file?
60
- custom_template_file && File.exists?(custom_template_file)
60
+ custom_template_file && File.exist?(custom_template_file)
61
61
  end
62
62
 
63
63
  def template_file
@@ -34,8 +34,6 @@ module Honeybadger
34
34
  config.api_key = 'project api key'
35
35
  config.exceptions.ignore += [CustomError]
36
36
  end
37
-
38
- See https://git.io/v1Sd4 for documentation.
39
37
  WARNING
40
38
  end
41
39
  end
@@ -1,6 +1,8 @@
1
1
  require 'bigdecimal'
2
2
  require 'set'
3
3
 
4
+ require 'honeybadger/conversions'
5
+
4
6
  module Honeybadger
5
7
  module Util
6
8
  # Internal: Sanitizer sanitizes data for sending to Honeybadger's API. The
@@ -12,14 +14,17 @@ module Honeybadger
12
14
 
13
15
  ENCODE_OPTS = { invalid: :replace, undef: :replace, replace: '?'.freeze }.freeze
14
16
 
17
+ BASIC_OBJECT = '#<BasicObject>'.freeze
18
+ DEPTH = '[DEPTH]'.freeze
15
19
  FILTERED = '[FILTERED]'.freeze
20
+ RAISED = '[RAISED]'.freeze
21
+ RECURSION = '[RECURSION]'.freeze
22
+ TRUNCATED = '[TRUNCATED]'.freeze
16
23
 
17
24
  IMMUTABLE = [NilClass, FalseClass, TrueClass, Symbol, Numeric, BigDecimal, Method].freeze
18
25
 
19
26
  MAX_STRING_SIZE = 65536
20
27
 
21
- TRUNCATION_REPLACEMENT = '[TRUNCATED]'.freeze
22
-
23
28
  VALID_ENCODINGS = [Encoding::UTF_8, Encoding::ISO_8859_1].freeze
24
29
 
25
30
  def self.sanitize(data)
@@ -52,36 +57,47 @@ module Honeybadger
52
57
  end
53
58
 
54
59
  def sanitize(data, depth = 0, stack = nil, parents = [])
55
- if enumerable?(data)
56
- return '[possible infinite recursion halted]'.freeze if stack && stack.include?(data.object_id)
60
+ return BASIC_OBJECT if basic_object?(data)
61
+
62
+ if recursive?(data)
63
+ return RECURSION if stack && stack.include?(data.object_id)
64
+
57
65
  stack = stack ? stack.dup : Set.new
58
66
  stack << data.object_id
59
67
  end
60
68
 
61
69
  case data
62
70
  when Hash
63
- return '[max depth reached]'.freeze if depth >= max_depth
71
+ return DEPTH if depth >= max_depth
72
+
64
73
  hash = data.to_hash
65
74
  new_hash = {}
75
+
66
76
  hash.each_pair do |key, value|
67
77
  parents.push(key) if deep_regexps
68
78
  key = key.kind_of?(Symbol) ? key : sanitize(key, depth+1, stack, parents)
79
+
69
80
  if filter_key?(key, parents)
70
81
  new_hash[key] = FILTERED
71
82
  else
72
83
  value = sanitize(value, depth+1, stack, parents)
73
- if blocks.any? && !enumerable?(value)
84
+
85
+ if blocks.any? && !recursive?(value)
74
86
  key = key.dup if can_dup?(key)
75
87
  value = value.dup if can_dup?(value)
76
88
  blocks.each { |b| b.call(key, value) }
77
89
  end
90
+
78
91
  new_hash[key] = value
79
92
  end
93
+
80
94
  parents.pop if deep_regexps
81
95
  end
96
+
82
97
  new_hash
83
98
  when Array, Set
84
- return '[max depth reached]'.freeze if depth >= max_depth
99
+ return DEPTH if depth >= max_depth
100
+
85
101
  data.to_a.map do |value|
86
102
  sanitize(value, depth+1, stack, parents)
87
103
  end
@@ -89,15 +105,27 @@ module Honeybadger
89
105
  data
90
106
  when String
91
107
  sanitize_string(data)
92
- else # all other objects:
93
- data.respond_to?(:to_s) ? sanitize_string(data.to_s) : nil
94
- end
95
- end
108
+ when -> (d) { d.respond_to?(:to_honeybadger) }
109
+ begin
110
+ data = data.to_honeybadger
111
+ rescue
112
+ return RAISED
113
+ end
96
114
 
97
- def sanitize_string(string)
98
- string = valid_encoding(string.to_s)
99
- return string unless string.respond_to?(:size) && string.size > MAX_STRING_SIZE
100
- string[0...MAX_STRING_SIZE] + TRUNCATION_REPLACEMENT
115
+ sanitize(data, depth+1, stack, parents)
116
+ else # all other objects
117
+ klass = data.class
118
+
119
+ begin
120
+ data = String(data)
121
+ rescue
122
+ return RAISED
123
+ end
124
+
125
+ return "#<#{klass.name}>" if inspected?(data)
126
+
127
+ sanitize_string(data)
128
+ end
101
129
  end
102
130
 
103
131
  def filter_cookies(raw_cookies)
@@ -142,6 +170,12 @@ module Honeybadger
142
170
  false
143
171
  end
144
172
 
173
+ def sanitize_string(string)
174
+ string = valid_encoding(string)
175
+ return string unless string.respond_to?(:size) && string.size > MAX_STRING_SIZE
176
+ string[0...MAX_STRING_SIZE] + TRUNCATED
177
+ end
178
+
145
179
  def valid_encoding?(string)
146
180
  string.valid_encoding? && (
147
181
  VALID_ENCODINGS.include?(string.encoding) ||
@@ -154,13 +188,25 @@ module Honeybadger
154
188
  string.encode(Encoding::UTF_8, ENCODE_OPTS)
155
189
  end
156
190
 
157
- def enumerable?(data)
158
- data.kind_of?(Hash) || data.kind_of?(Array) || data.kind_of?(Set)
191
+ def recursive?(data)
192
+ data.is_a?(Hash) || data.is_a?(Array) || data.is_a?(Set) || data.respond_to?(:to_honeybadger)
193
+ end
194
+
195
+ def basic_object?(object)
196
+ object.respond_to?(:to_s)
197
+ false
198
+ rescue
199
+ # BasicObject doesn't respond to `#respond_to?`.
200
+ true
159
201
  end
160
202
 
161
203
  def can_dup?(obj)
162
204
  !IMMUTABLE.any? {|k| obj.kind_of?(k) }
163
205
  end
206
+
207
+ def inspected?(string)
208
+ String(string) =~ /#<.*>/
209
+ end
164
210
  end
165
211
  end
166
212
  end