honeybadger 2.0.6 → 2.0.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 45184c861193f93cac6f5079c6d0501f44fb1e29
4
- data.tar.gz: 5dc11986399b1c0e712b3102358a76b4b65616a4
3
+ metadata.gz: 5f7f7f0ed185071b638c1397c651de08defdd67f
4
+ data.tar.gz: dc426f168bc4fc433638826c744df12f2f11e137
5
5
  SHA512:
6
- metadata.gz: 7b0b237ca43d56e272df5dce38446dbd73d15c3979e7aa749d839b89dfb18ae827d405931b7c2542f428e89fcac373ddd340d6b07167765cb8cd7d7f6fc9183e
7
- data.tar.gz: 934975d16d06daf5215c5b34ba97127cafad4df667d2c8d508b445fac36c1b2e44ab4bb3e6a9a0ff3acf2e739068a7b373b21c4a0172e896fbdbd08d8e85250d
6
+ metadata.gz: 19f26d5077e250b8faee7309504bc93ae568d87f8fb5eed8274a4810181dcb865e6688190bfba68c43cc3ac5dbd405f6ddd9ebc98aacf4e736605386c2a6b943
7
+ data.tar.gz: 1227af632c510f6255b24766622a921a5f2487d71450453a700db852da47cf4b0991b27b11714b0c71227ce224bd9d55a7701c90596704fdbd81fe405b278c35
data/CHANGELOG.md CHANGED
@@ -1,3 +1,19 @@
1
+ * Handle bad encodings in exception payloads.
2
+
3
+ *Joshua Wood*
4
+
5
+ * Include full backtrace when logging worker exceptions.
6
+
7
+ *Joshua Wood*
8
+
9
+ * Always send a test notice on install.
10
+
11
+ *Joshua Wood*
12
+
13
+ * Send the id of the current process with error reports.
14
+
15
+ *Joshua Wood*
16
+
1
17
  * Don't sub partial project root in backtrace lines.
2
18
 
3
19
  *Joshua Wood*
@@ -282,7 +282,10 @@ module Honeybadger
282
282
  def run
283
283
  loop { work }
284
284
  rescue Exception => e
285
- error(sprintf('error in agent thread (shutting down) class=%s message=%s at=%s', e.class, e.message.dump, e.backtrace.first.dump))
285
+ error {
286
+ msg = "error in agent thread (shutting down) class=%s message=%s\n\t%s"
287
+ sprintf(msg, e.class, e.message.dump, Array(e.backtrace).join("\n\t"))
288
+ }
286
289
  ensure
287
290
  d { sprintf('stopping agent') }
288
291
  end
@@ -291,7 +294,10 @@ module Honeybadger
291
294
  flush_metrics if metrics.flush?
292
295
  flush_traces if traces.flush?
293
296
  rescue StandardError => e
294
- error(sprintf('error in agent thread class=%s message=%s at=%s', e.class, e.message.dump, e.backtrace.first.dump))
297
+ error {
298
+ msg = "error in agent thread class=%s message=%s\n\t%s"
299
+ sprintf(msg, e.class, e.message.dump, Array(e.backtrace).join("\n\t"))
300
+ }
295
301
  ensure
296
302
  sleep(delay)
297
303
  end
@@ -131,7 +131,10 @@ module Honeybadger
131
131
  d { sprintf('stopping worker feature=%s', feature) }
132
132
  end
133
133
  rescue Exception => e
134
- error(sprintf('error in worker thread (shutting down) feature=%s class=%s message=%s at=%s', feature, e.class, e.message.dump, e.backtrace.first.dump))
134
+ error {
135
+ msg = "error in worker thread (shutting down) feature=%s class=%s message=%s\n\t%s"
136
+ sprintf(msg, feature, e.class, e.message.dump, Array(e.backtrace).join("\n\t"))
137
+ }
135
138
  ensure
136
139
  release_marker
137
140
  end
@@ -140,7 +143,10 @@ module Honeybadger
140
143
  handle_response(notify_backend(msg))
141
144
  sleep(throttle_interval)
142
145
  rescue StandardError => e
143
- error(sprintf('error in worker thread feature=%s class=%s message=%s at=%s', feature, e.class, e.message.dump, e.backtrace.first.dump))
146
+ error {
147
+ msg = "error in worker thread feature=%s class=%s message=%s\n\t%s"
148
+ sprintf(msg, feature, e.class, e.message.dump, Array(e.backtrace).join("\n\t"))
149
+ }
144
150
  sleep(1)
145
151
  end
146
152
 
@@ -125,7 +125,7 @@ module Honeybadger
125
125
 
126
126
  def test_honeybadger
127
127
  puts "Raising '#{test_exception_class.name}' to simulate application failure."
128
- raise #{test_exception_class}.new, 'Testing honeybadger via "honeybadger testhoneybadger test", it works.'
128
+ raise #{test_exception_class}.new, 'Testing honeybadger via "honeybadger test", it works.'
129
129
  end
130
130
 
131
131
  # Ensure we actually have an action to go to.
@@ -127,7 +127,6 @@ module Honeybadger
127
127
 
128
128
  if (path = config.config_path).exist?
129
129
  say("You're already on Honeybadger, so you're all set.", :yellow)
130
- skip_test = true if options[:test].nil? # Only if it wasn't specified.
131
130
  else
132
131
  say("Writing configuration to: #{path}", :yellow)
133
132
 
@@ -153,7 +152,7 @@ module Honeybadger
153
152
  end
154
153
  end
155
154
 
156
- if !skip_test && (options[:test].nil? || options[:test])
155
+ if options[:test].nil? || options[:test]
157
156
  Honeybadger.start(config) unless load_rails_env(verbose: true)
158
157
  say('Sending test notice', :yellow)
159
158
  unless Agent.instance && send_test(false)
@@ -352,7 +352,13 @@ api_key: '#{self[:api_key]}'
352
352
  end
353
353
  end
354
354
  rescue ConfigError => e
355
- logger.error("Error while loading config from disk: #{e}")
355
+ error("error while loading config from disk: #{e}")
356
+ nil
357
+ rescue StandardError => e
358
+ error {
359
+ msg = "error while loading config from disk class=%s message=%s\n\t%s"
360
+ sprintf(msg, e.class, e.message.dump, Array(e.backtrace).join("\n\t"))
361
+ }
356
362
  nil
357
363
  end
358
364
 
@@ -19,9 +19,6 @@ module Honeybadger
19
19
  yaml.merge!(yaml[env]) if yaml[env].kind_of?(Hash)
20
20
  update(dotify_keys(yaml))
21
21
  end
22
-
23
- rescue StandardError => e
24
- raise ConfigError, "An unknown error occured: #{e.class} -- #{e.message}\n\t#{e.backtrace.first}"
25
22
  end
26
23
 
27
24
  private
@@ -54,7 +54,7 @@ module Honeybadger
54
54
  end
55
55
 
56
56
  def level
57
- Logger::Severity::Debug
57
+ Logger::Severity::DEBUG
58
58
  end
59
59
  end
60
60
 
@@ -7,7 +7,6 @@ require 'honeybadger/version'
7
7
  require 'honeybadger/backtrace'
8
8
  require 'honeybadger/util/stats'
9
9
  require 'honeybadger/util/sanitizer'
10
- require 'honeybadger/util/request_sanitizer'
11
10
  require 'honeybadger/rack/request_hash'
12
11
 
13
12
  module Honeybadger
@@ -132,10 +131,12 @@ module Honeybadger
132
131
 
133
132
  def initialize(config, opts = {})
134
133
  @now = Time.now.utc
134
+ @pid = Process.pid
135
135
  @id = SecureRandom.uuid
136
136
 
137
137
  @opts = opts
138
138
  @config = config
139
+ @sanitizer = Util::Sanitizer.new(filters: config.params_filters)
139
140
 
140
141
  @exception = opts[:exception]
141
142
  @error_class = exception_attribute(:error_class) {|exception| exception.class.name }
@@ -148,10 +149,9 @@ module Honeybadger
148
149
  @source = extract_source_from_backtrace(@backtrace, config, opts)
149
150
  @fingerprint = construct_fingerprint(opts)
150
151
 
151
- @sanitizer = Util::Sanitizer.new(filters: config.params_filters)
152
- @request_sanitizer = Util::RequestSanitizer.new(@sanitizer)
153
- @request = OpenStruct.new(construct_request_hash(config.request, opts, @request_sanitizer, config.excluded_request_keys))
154
- @context = construct_context_hash(opts, @sanitizer)
152
+ @request = OpenStruct.new(construct_request_hash(config.request, opts, config.excluded_request_keys))
153
+
154
+ @context = construct_context_hash(opts)
155
155
 
156
156
  @causes = unwrap_causes(opts[:exception])
157
157
 
@@ -160,7 +160,7 @@ module Honeybadger
160
160
 
161
161
  @stats = Util::Stats.all
162
162
 
163
- @local_variables = local_variables_from_exception(exception, config, @sanitizer)
163
+ @local_variables = local_variables_from_exception(exception, config)
164
164
 
165
165
  @api_key = opts[:api_key] || config[:api_key]
166
166
 
@@ -172,34 +172,35 @@ module Honeybadger
172
172
  # Returns Hash JSON representation of notice
173
173
  def as_json(*args)
174
174
  {
175
- api_key: api_key,
175
+ api_key: s(api_key),
176
176
  notifier: NOTIFIER,
177
177
  error: {
178
178
  token: id,
179
- class: error_class,
180
- message: error_message,
181
- backtrace: backtrace,
182
- source: source,
183
- fingerprint: fingerprint,
184
- tags: tags,
185
- causes: causes
179
+ class: s(error_class),
180
+ message: s(error_message),
181
+ backtrace: s(backtrace),
182
+ source: s(source),
183
+ fingerprint: s(fingerprint),
184
+ tags: s(tags),
185
+ causes: s(causes)
186
186
  },
187
187
  request: {
188
- url: url,
189
- component: component,
190
- action: action,
191
- params: params,
192
- session: session,
193
- cgi_data: cgi_data,
194
- context: context,
195
- local_variables: local_variables
188
+ url: sanitized_url,
189
+ component: s(component),
190
+ action: s(action),
191
+ params: s(params),
192
+ session: s(session),
193
+ cgi_data: s(cgi_data),
194
+ context: s(context),
195
+ local_variables: s(local_variables)
196
196
  },
197
197
  server: {
198
- project_root: config[:root],
199
- environment_name: config[:env],
200
- hostname: config[:hostname],
198
+ project_root: s(config[:root]),
199
+ environment_name: s(config[:env]),
200
+ hostname: s(config[:hostname]),
201
201
  stats: stats,
202
- time: now
202
+ time: now,
203
+ pid: pid
203
204
  }
204
205
  }
205
206
  end
@@ -208,7 +209,7 @@ module Honeybadger
208
209
  #
209
210
  # Returns valid JSON representation of Notice
210
211
  def to_json(*a)
211
- as_json.to_json(*a)
212
+ ::JSON.generate(as_json(*a))
212
213
  end
213
214
 
214
215
  # Public: Allows properties to be accessed using a hash-like syntax
@@ -236,7 +237,7 @@ module Honeybadger
236
237
 
237
238
  private
238
239
 
239
- attr_reader :config, :opts, :context, :stats, :api_key, :now, :causes
240
+ attr_reader :config, :opts, :context, :stats, :api_key, :now, :pid, :causes, :sanitizer
240
241
 
241
242
  def ignore_by_origin?
242
243
  opts[:origin] == :rake && !config[:'exceptions.rescue_rake']
@@ -328,7 +329,7 @@ module Honeybadger
328
329
  ].compact | BACKTRACE_FILTERS
329
330
  end
330
331
 
331
- def construct_request_hash(rack_request, opts, sanitizer, excluded_keys = [])
332
+ def construct_request_hash(rack_request, opts, excluded_keys = [])
332
333
  request = {}
333
334
  request.merge!(Rack::RequestHash.new(rack_request)) if rack_request
334
335
 
@@ -342,14 +343,14 @@ module Honeybadger
342
343
 
343
344
  request[:session] = request[:session][:data] if request[:session][:data]
344
345
 
345
- sanitizer.sanitize(request)
346
+ request
346
347
  end
347
348
 
348
- def construct_context_hash(opts, sanitizer)
349
+ def construct_context_hash(opts)
349
350
  context = {}
350
351
  context.merge!(Thread.current[:__honeybadger_context]) if Thread.current[:__honeybadger_context]
351
352
  context.merge!(opts[:context]) if opts[:context]
352
- context.empty? ? nil : sanitizer.sanitize(context)
353
+ context.empty? ? nil : context
353
354
  end
354
355
 
355
356
  def extract_source_from_backtrace(backtrace, config, opts)
@@ -401,12 +402,21 @@ module Honeybadger
401
402
  ret
402
403
  end
403
404
 
405
+ def s(data)
406
+ sanitizer.sanitize(data)
407
+ end
408
+
409
+ def sanitized_url
410
+ return nil unless url
411
+ sanitizer.filter_url(s(url))
412
+ end
413
+
404
414
  # Internal: Fetch local variables from first frame of backtrace.
405
415
  #
406
416
  # exception - The Exception containing the bindings stack.
407
417
  #
408
418
  # Returns a Hash of local variables
409
- def local_variables_from_exception(exception, config, sanitizer)
419
+ def local_variables_from_exception(exception, config)
410
420
  return {} unless send_local_variables?(config)
411
421
  return {} unless Exception === exception
412
422
  return {} unless exception.respond_to?(:__honeybadger_bindings_stack)
@@ -419,9 +429,7 @@ module Honeybadger
419
429
  binding ||= exception.__honeybadger_bindings_stack[0]
420
430
 
421
431
  vars = binding.eval('local_variables')
422
- h = Hash[vars.map {|arg| [arg, binding.eval(arg.to_s)]}]
423
-
424
- sanitizer.sanitize(h)
432
+ Hash[vars.map {|arg| [arg, binding.eval(arg.to_s)]}]
425
433
  end
426
434
 
427
435
  # Internal: Should local variables be sent?
@@ -71,7 +71,7 @@ module Honeybadger
71
71
  def ok?(config)
72
72
  @requirements.all? {|r| Execution.new(config, &r).call }
73
73
  rescue => e
74
- config.logger.error(sprintf('plugin error name=%s class=%s message=%s at=%s', name, e.class, e.message.dump, e.backtrace.first.dump))
74
+ config.logger.error(sprintf("plugin error name=%s class=%s message=%s\n\t%s", name, e.class, e.message.dump, Array(e.backtrace).join("\n\t")))
75
75
  false
76
76
  end
77
77
 
@@ -89,7 +89,7 @@ module Honeybadger
89
89
 
90
90
  @loaded
91
91
  rescue => e
92
- config.logger.error(sprintf('plugin error name=%s class=%s message=%s at=%s', name, e.class, e.message.dump, e.backtrace.first.dump))
92
+ config.logger.error(sprintf("plugin error name=%s class=%s message=%s\n\t%s", name, e.class, e.message.dump, Array(e.backtrace).join("\n\t")))
93
93
  @loaded = true
94
94
  false
95
95
  end
@@ -64,11 +64,6 @@ module Honeybadger
64
64
  end
65
65
 
66
66
  http
67
- rescue => e
68
- error do
69
- sprintf('http error class=%s message=%s at=%s', e.class, e.message.dump, e.backtrace.first.dump)
70
- end
71
- raise e
72
67
  end
73
68
 
74
69
  def compress(string, level = Zlib::DEFAULT_COMPRESSION)
@@ -3,41 +3,46 @@ module Honeybadger
3
3
  class Sanitizer
4
4
  OBJECT_WHITELIST = [Hash, Array, String, Integer, Float, TrueClass, FalseClass, NilClass]
5
5
 
6
+ FILTERED_REPLACEMENT = '[FILTERED]'.freeze
7
+
6
8
  def initialize(opts = {})
7
- @max_depth = opts[:max_depth] || 20
8
- @filters = Array(opts[:filters])
9
+ @max_depth = opts.fetch(:max_depth, 20)
10
+ @filters = Array(opts.fetch(:filters, nil)).collect do |f|
11
+ f.kind_of?(Regexp) ? f : f.to_s
12
+ end
9
13
  end
10
14
 
11
- # Removes non-serializable data and truncates to max depth.
12
- def sanitize(data, depth = 0, stack = [])
13
- return '[possible infinite recursion halted]' if stack.any?{|item| item == data.object_id }
15
+ def sanitize(data, depth = 0, stack = nil)
16
+ if recursive?(data)
17
+ return '[possible infinite recursion halted]' if stack && stack.include?(data.object_id)
18
+ stack = stack ? stack.dup : Set.new
19
+ stack << data.object_id
20
+ end
14
21
 
15
- if data.respond_to?(:to_hash)
22
+ if data.kind_of?(String)
23
+ sanitize_string(data)
24
+ elsif data.respond_to?(:to_hash)
16
25
  return '[max depth reached]' if depth >= max_depth
17
- data.to_hash.reduce({}) do |result, (key, value)|
18
- result.merge(key => sanitize(value, depth+1, stack + [data.object_id]))
26
+ hash = data.to_hash
27
+ new_hash = {}
28
+ hash.each_pair do |key, value|
29
+ k = key.kind_of?(Symbol) ? key : sanitize(key, depth+1, stack)
30
+ if filter_key?(k)
31
+ new_hash[k] = FILTERED_REPLACEMENT
32
+ else
33
+ new_hash[k] = sanitize(value, depth+1, stack)
34
+ end
19
35
  end
36
+ new_hash
20
37
  elsif data.respond_to?(:to_ary)
21
38
  return '[max depth reached]' if depth >= max_depth
22
- data.to_ary.collect do |value|
23
- sanitize(value, depth+1, stack + [data.object_id])
24
- end
39
+ data.to_ary.map do |value|
40
+ sanitize(value, depth+1, stack)
41
+ end.compact
25
42
  elsif OBJECT_WHITELIST.any? {|c| data.kind_of?(c) }
26
43
  data
27
44
  else
28
- data.to_s
29
- end
30
- end
31
-
32
- def filter(hash)
33
- {}.tap do |filtered_hash|
34
- hash.each_pair do |key, value|
35
- if value.respond_to?(:to_hash)
36
- filtered_hash[key] = filter(hash[key])
37
- else
38
- filtered_hash[key] = filter_key?(key) ? '[FILTERED]' : hash[key]
39
- end
40
- end
45
+ sanitize_string(data.to_s)
41
46
  end
42
47
  end
43
48
 
@@ -53,19 +58,44 @@ module Honeybadger
53
58
 
54
59
  private
55
60
 
61
+ VALID_ENCODINGS = [Encoding::UTF_8, Encoding::ISO_8859_1].freeze
62
+ ENCODE_OPTS = { invalid: :replace, undef: :replace, replace: '?'.freeze }.freeze
63
+ UTF8_STRING = ''.freeze
64
+
56
65
  attr_reader :max_depth, :filters
57
66
 
67
+ def recursive?(data)
68
+ data.respond_to?(:to_hash) || data.respond_to?(:to_ary)
69
+ end
70
+
58
71
  def filter_key?(key)
59
72
  return false unless filters
60
73
 
61
74
  filters.any? do |filter|
62
75
  if filter.is_a?(Regexp)
63
- key.to_s =~ filter
76
+ filter =~ key.to_s
64
77
  else
65
78
  key.to_s.eql?(filter.to_s)
66
79
  end
67
80
  end
68
81
  end
82
+
83
+ def valid_encoding?(data)
84
+ data.valid_encoding? && (
85
+ VALID_ENCODINGS.include?(data.encoding) ||
86
+ VALID_ENCODINGS.include?(Encoding.compatible?(UTF8_STRING, data))
87
+ )
88
+ end
89
+
90
+ def sanitize_string(data)
91
+ return data if valid_encoding?(data)
92
+
93
+ if data.encoding == Encoding::UTF_8
94
+ data.encode(Encoding::UTF_16, ENCODE_OPTS).encode!(Encoding::UTF_8)
95
+ else
96
+ data.encode(Encoding::UTF_8, ENCODE_OPTS)
97
+ end
98
+ end
69
99
  end
70
100
  end
71
101
  end
@@ -1,4 +1,4 @@
1
1
  module Honeybadger
2
2
  # Public: The current String Honeybadger version.
3
- VERSION = '2.0.6'.freeze
3
+ VERSION = '2.0.8'.freeze
4
4
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: honeybadger
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.6
4
+ version: 2.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Honeybadger Industries LLC
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-16 00:00:00.000000000 Z
11
+ date: 2015-03-11 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Make managing application errors a more pleasant experience.
14
14
  email:
@@ -73,7 +73,6 @@ files:
73
73
  - lib/honeybadger/templates/feedback_form.erb
74
74
  - lib/honeybadger/trace.rb
75
75
  - lib/honeybadger/util/http.rb
76
- - lib/honeybadger/util/request_sanitizer.rb
77
76
  - lib/honeybadger/util/sanitizer.rb
78
77
  - lib/honeybadger/util/stats.rb
79
78
  - lib/honeybadger/version.rb
@@ -1,35 +0,0 @@
1
- module Honeybadger
2
- module Util
3
- class RequestSanitizer
4
-
5
- def initialize(sanitizer)
6
- @sanitizer = sanitizer
7
- end
8
-
9
- def sanitize(request_hash)
10
- request_hash.merge({
11
- url: sanitize_url(request_hash[:url]),
12
- component: request_hash[:component],
13
- action: request_hash[:action],
14
- params: sanitize_hash(request_hash[:params]),
15
- session: sanitize_hash(request_hash[:session]),
16
- cgi_data: sanitize_hash(request_hash[:cgi_data])
17
- })
18
- end
19
-
20
- private
21
-
22
- def sanitize_url(url)
23
- if url
24
- @sanitizer.filter_url(url)
25
- end
26
- end
27
-
28
- def sanitize_hash(hash)
29
- if hash
30
- @sanitizer.filter(@sanitizer.sanitize(hash))
31
- end
32
- end
33
- end
34
- end
35
- end