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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -0
- data/README.md +2 -750
- data/TROUBLESHOOTING.md +1 -135
- data/lib/honeybadger/agent.rb +48 -22
- data/lib/honeybadger/backend/base.rb +4 -4
- data/lib/honeybadger/backend/null.rb +11 -1
- data/lib/honeybadger/backend/test.rb +1 -1
- data/lib/honeybadger/backtrace.rb +1 -1
- data/lib/honeybadger/cli/main.rb +1 -1
- data/lib/honeybadger/cli/test.rb +9 -9
- data/lib/honeybadger/config.rb +6 -2
- data/lib/honeybadger/context_manager.rb +4 -1
- data/lib/honeybadger/conversions.rb +15 -0
- data/lib/honeybadger/logging.rb +2 -2
- data/lib/honeybadger/notice.rb +54 -39
- data/lib/honeybadger/plugins/sidekiq.rb +3 -2
- data/lib/honeybadger/rack/error_notifier.rb +1 -1
- data/lib/honeybadger/rack/user_feedback.rb +1 -1
- data/lib/honeybadger/singleton.rb +0 -2
- data/lib/honeybadger/util/sanitizer.rb +63 -17
- data/lib/honeybadger/util/stats.rb +2 -2
- data/lib/honeybadger/version.rb +1 -1
- data/lib/honeybadger/worker.rb +2 -0
- metadata +5 -4
@@ -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
|
data/lib/honeybadger/logging.rb
CHANGED
@@ -85,7 +85,7 @@ module Honeybadger
|
|
85
85
|
class StandardLogger < Base
|
86
86
|
extend Forwardable
|
87
87
|
|
88
|
-
def initialize(logger = Logger.new(
|
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(
|
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')
|
data/lib/honeybadger/notice.rb
CHANGED
@@ -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
|
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
|
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
|
-
@
|
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
|
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
|
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
|
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])
|
323
|
-
context.merge!(
|
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:
|
456
|
+
# Internal: Create a list of causes.
|
444
457
|
#
|
445
|
-
#
|
458
|
+
# cause - The first cause to unwrap.
|
446
459
|
#
|
447
|
-
# Returns
|
448
|
-
def unwrap_causes(
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
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
|
-
|
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
|
-
|
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] =
|
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
|
@@ -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
|
56
|
-
|
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
|
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
|
-
|
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
|
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
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
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
|
158
|
-
data.
|
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
|