honeybadger 2.0.6 → 2.0.8

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