honeybadger 3.1.2 → 3.2.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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