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.
- 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
|