honeybadger 2.7.2 → 3.0.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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +58 -3
  3. data/README.md +8 -15
  4. data/lib/honeybadger.rb +9 -232
  5. data/lib/honeybadger/agent.rb +292 -134
  6. data/lib/honeybadger/backend.rb +6 -6
  7. data/lib/honeybadger/backend/base.rb +11 -0
  8. data/lib/honeybadger/backend/server.rb +2 -14
  9. data/lib/honeybadger/cli.rb +0 -2
  10. data/lib/honeybadger/cli/deploy.rb +42 -0
  11. data/lib/honeybadger/cli/exec.rb +138 -0
  12. data/lib/honeybadger/cli/heroku.rb +1 -22
  13. data/lib/honeybadger/cli/install.rb +74 -0
  14. data/lib/honeybadger/cli/main.rb +138 -153
  15. data/lib/honeybadger/cli/notify.rb +66 -0
  16. data/lib/honeybadger/cli/test.rb +266 -0
  17. data/lib/honeybadger/config.rb +178 -162
  18. data/lib/honeybadger/config/defaults.rb +5 -5
  19. data/lib/honeybadger/config/env.rb +8 -6
  20. data/lib/honeybadger/config/ruby.rb +100 -0
  21. data/lib/honeybadger/config/yaml.rb +18 -19
  22. data/lib/honeybadger/const.rb +3 -16
  23. data/lib/honeybadger/context_manager.rb +50 -0
  24. data/lib/honeybadger/init/rails.rb +9 -21
  25. data/lib/honeybadger/init/rake.rb +2 -0
  26. data/lib/honeybadger/init/ruby.rb +9 -0
  27. data/lib/honeybadger/init/sinatra.rb +13 -6
  28. data/lib/honeybadger/notice.rb +29 -14
  29. data/lib/honeybadger/plugins/delayed_job/plugin.rb +4 -5
  30. data/lib/honeybadger/plugins/passenger.rb +1 -2
  31. data/lib/honeybadger/plugins/rails.rb +0 -28
  32. data/lib/honeybadger/plugins/resque.rb +2 -5
  33. data/lib/honeybadger/plugins/shoryuken.rb +2 -2
  34. data/lib/honeybadger/plugins/sidekiq.rb +2 -2
  35. data/lib/honeybadger/plugins/sucker_punch.rb +1 -0
  36. data/lib/honeybadger/plugins/thor.rb +2 -2
  37. data/lib/honeybadger/plugins/warden.rb +1 -0
  38. data/lib/honeybadger/rack/error_notifier.rb +11 -9
  39. data/lib/honeybadger/rack/user_feedback.rb +6 -4
  40. data/lib/honeybadger/rack/user_informer.rb +6 -4
  41. data/lib/honeybadger/ruby.rb +2 -0
  42. data/lib/honeybadger/singleton.rb +26 -0
  43. data/lib/honeybadger/util/http.rb +12 -0
  44. data/lib/honeybadger/util/request_hash.rb +71 -0
  45. data/lib/honeybadger/util/sanitizer.rb +101 -64
  46. data/lib/honeybadger/version.rb +1 -1
  47. data/lib/honeybadger/worker.rb +246 -0
  48. metadata +17 -13
  49. data/lib/honeybadger/agent/batch.rb +0 -50
  50. data/lib/honeybadger/agent/null_worker.rb +0 -26
  51. data/lib/honeybadger/agent/worker.rb +0 -243
  52. data/lib/honeybadger/cli/helpers.rb +0 -160
  53. data/lib/honeybadger/config/callbacks.rb +0 -70
  54. data/lib/honeybadger/plugins/unicorn.rb +0 -27
  55. data/lib/honeybadger/rack/metrics_reporter.rb +0 -16
  56. data/lib/honeybadger/rack/request_hash.rb +0 -55
@@ -23,7 +23,7 @@ module Honeybadger
23
23
 
24
24
  DEVELOPMENT_ENVIRONMENTS = ['development', 'test', 'cucumber'].map(&:freeze).freeze
25
25
 
26
- DEFAULT_PATHS = ['honeybadger.yml', 'config/honeybadger.yml'].map(&:freeze).freeze
26
+ DEFAULT_PATHS = ['honeybadger.yml', 'config/honeybadger.yml', "#{ENV['HOME']}/honeybadger.yml"].map(&:freeze).freeze
27
27
 
28
28
  OPTIONS = {
29
29
  api_key: {
@@ -33,7 +33,7 @@ module Honeybadger
33
33
  },
34
34
  env: {
35
35
  description: 'The current application\'s environment name.',
36
- default: ENV['HONEYBADGER_ENV'] || ENV['RACK_ENV'] || ENV['APP_ENV'],
36
+ default: nil,
37
37
  type: String
38
38
  },
39
39
  report_data: {
@@ -86,7 +86,7 @@ module Honeybadger
86
86
  default: nil,
87
87
  type: Array
88
88
  },
89
- :'plugins.skip' => {
89
+ :'skipped_plugins' => {
90
90
  description: 'An optional list of plugins to skip.',
91
91
  default: nil,
92
92
  type: Array
@@ -223,7 +223,7 @@ module Honeybadger
223
223
  },
224
224
  :'exceptions.ignore_only' => {
225
225
  description: 'A list of exceptions to ignore (overrides the default ignored exceptions).',
226
- default: [].freeze,
226
+ default: nil,
227
227
  type: Array
228
228
  },
229
229
  :'exceptions.ignored_user_agents' => {
@@ -268,7 +268,7 @@ module Honeybadger
268
268
  },
269
269
  :'sidekiq.use_component' => {
270
270
  description: 'Automatically set the component to the class of the job. Helps with grouping.',
271
- default: false,
271
+ default: true,
272
272
  type: Boolean
273
273
  },
274
274
  :'sinatra.enabled' => {
@@ -1,21 +1,23 @@
1
1
  module Honeybadger
2
2
  class Config
3
- class Env < ::Hash
3
+ module Env
4
4
  CONFIG_KEY = /\AHONEYBADGER_(.+)\Z/.freeze
5
5
  CONFIG_MAPPING = Hash[DEFAULTS.keys.map {|k| [k.to_s.upcase.gsub(KEY_REPLACEMENT, '_'), k] }].freeze
6
6
  ARRAY_VALUES = Regexp.new('\s*,\s*').freeze
7
7
 
8
- def initialize(env = ENV)
8
+ def self.new(env = ENV)
9
+ hash = {}
10
+
9
11
  env.each_pair do |k,v|
10
12
  next unless k.match(CONFIG_KEY)
11
13
  next unless config_key = CONFIG_MAPPING[$1]
12
- self[config_key] = cast_value(v, OPTIONS[config_key][:type])
14
+ hash[config_key] = cast_value(v, OPTIONS[config_key][:type])
13
15
  end
14
- end
15
16
 
16
- private
17
+ hash
18
+ end
17
19
 
18
- def cast_value(value, type = String)
20
+ def self.cast_value(value, type = String)
19
21
  v = value.to_s
20
22
 
21
23
  if type == Boolean
@@ -0,0 +1,100 @@
1
+ module Honeybadger
2
+ class Config
3
+ class Mash
4
+ KEYS = DEFAULTS.keys.map(&:to_s).freeze
5
+
6
+ def initialize(config, prefix: nil, hash: {})
7
+ @config = config
8
+ @prefix = prefix
9
+ @hash = hash
10
+ end
11
+
12
+ def to_hash
13
+ hash.to_hash
14
+ end
15
+ alias to_h to_hash
16
+
17
+ private
18
+
19
+ attr_reader :config, :prefix, :hash
20
+
21
+ def method_missing(method_name, *args, &block)
22
+ m = method_name.to_s
23
+ if mash?(m)
24
+ return Mash.new(config, prefix: key(m), hash: hash)
25
+ elsif setter?(m)
26
+ return hash.send(:[]=, key(m).to_sym, args[0])
27
+ elsif getter?(m)
28
+ return get(key(m))
29
+ end
30
+
31
+ super
32
+ end
33
+
34
+ def respond_to_missing?(method_name, include_private = false)
35
+ true
36
+ end
37
+
38
+ def mash?(method)
39
+ key = [prefix, method.to_s + '.'].compact.join('.')
40
+ KEYS.any? {|k| k.start_with?(key) }
41
+ end
42
+
43
+ def setter?(method_name)
44
+ return false unless method_name.to_s =~ /=\z/
45
+ key = key(method_name)
46
+ KEYS.any? {|k| k == key }
47
+ end
48
+
49
+ def getter?(method_name)
50
+ key = key(method_name)
51
+ KEYS.any? {|k| k == key }
52
+ end
53
+
54
+ def key(method_name)
55
+ parts = [prefix, method_name.to_s.chomp('=')]
56
+ parts.compact!
57
+ parts.join('.')
58
+ end
59
+
60
+ def get(key)
61
+ k = key.to_sym
62
+ return hash[k] if hash.has_key?(k)
63
+ config.get(k)
64
+ end
65
+ end
66
+
67
+ class Ruby < Mash
68
+ def logger=(logger)
69
+ hash[:logger] = logger
70
+ end
71
+
72
+ def logger
73
+ get(:logger)
74
+ end
75
+
76
+ def backend=(backend)
77
+ hash[:backend] = backend
78
+ end
79
+
80
+ def backend
81
+ get(:backend)
82
+ end
83
+
84
+ def backtrace_filter
85
+ hash[:backtrace_filter] = Proc.new if block_given?
86
+ get(:backtrace_filter)
87
+ end
88
+
89
+ def exception_filter
90
+ hash[:exception_filter] = Proc.new if block_given?
91
+ get(:exception_filter)
92
+ end
93
+
94
+ def exception_fingerprint
95
+ hash[:exception_fingerprint] = Proc.new if block_given?
96
+ get(:exception_fingerprint)
97
+ end
98
+ end
99
+ end
100
+ end
@@ -4,40 +4,39 @@ require 'erb'
4
4
 
5
5
  module Honeybadger
6
6
  class Config
7
- class Yaml < ::Hash
7
+ module Yaml
8
8
  DISALLOWED_KEYS = [:'config.path'].freeze
9
9
 
10
- def initialize(path, env = 'production')
11
- @path = path.kind_of?(Pathname) ? path : Pathname.new(path)
10
+ def self.new(path, env = 'production')
11
+ path = path.kind_of?(Pathname) ? path : Pathname.new(path)
12
12
 
13
- if !@path.exist?
14
- raise ConfigError, "The configuration file #{@path} was not found."
15
- elsif !@path.file?
16
- raise ConfigError, "The configuration file #{@path} is not a file."
17
- elsif !@path.readable?
18
- raise ConfigError, "The configuration file #{@path} is not readable."
19
- else
20
- yaml = load_yaml
21
- yaml.merge!(yaml[env]) if yaml[env].kind_of?(Hash)
22
- update(dotify_keys(yaml))
13
+ if !path.exist?
14
+ raise ConfigError, "The configuration file #{path} was not found."
15
+ elsif !path.file?
16
+ raise ConfigError, "The configuration file #{path} is not a file."
17
+ elsif !path.readable?
18
+ raise ConfigError, "The configuration file #{path} is not readable."
23
19
  end
24
- end
25
20
 
26
- private
21
+ yaml = load_yaml(path)
22
+ yaml.merge!(yaml[env]) if yaml[env].kind_of?(Hash)
23
+
24
+ dotify_keys(yaml)
25
+ end
27
26
 
28
- def load_yaml
29
- yaml = YAML.load(ERB.new(@path.read).result)
27
+ def self.load_yaml(path)
28
+ yaml = YAML.load(ERB.new(path.read).result)
30
29
  case yaml
31
30
  when Hash
32
31
  yaml
33
32
  when NilClass, FalseClass
34
33
  {}
35
34
  else
36
- raise ConfigError, "The configuration file #{@path} is invalid."
35
+ raise ConfigError, "The configuration file #{path} is invalid."
37
36
  end
38
37
  end
39
38
 
40
- def dotify_keys(hash, key_prefix = nil)
39
+ def self.dotify_keys(hash, key_prefix = nil)
41
40
  {}.tap do |new_hash|
42
41
  hash.each_pair do |k,v|
43
42
  k = [key_prefix, k].compact.join('.')
@@ -1,25 +1,12 @@
1
1
  require 'honeybadger/version'
2
2
 
3
3
  module Honeybadger
4
- autoload :Agent, 'honeybadger/agent'
5
- autoload :Backend, 'honeybadger/backend'
6
- autoload :Backtrace, 'honeybadger/backtrace'
7
- autoload :Config, 'honeybadger/config'
8
- autoload :Logging, 'honeybadger/logging'
9
- autoload :Notice, 'honeybadger/notice'
10
- autoload :Plugin, 'honeybadger/plugin'
11
-
4
+ # Autoloading allows middleware classes to be referenced in applications
5
+ # which include the optional Rack dependency without explicitly requiring
6
+ # these files.
12
7
  module Rack
13
8
  autoload :ErrorNotifier, 'honeybadger/rack/error_notifier'
14
9
  autoload :UserFeedback, 'honeybadger/rack/user_feedback'
15
10
  autoload :UserInformer, 'honeybadger/rack/user_informer'
16
- autoload :RequestHash, 'honeybadger/rack/request_hash'
17
- end
18
-
19
- module Util
20
- autoload :Sanitizer, 'honeybadger/util/sanitizer'
21
- autoload :RequestSanitizer, 'honeybadger/util/request_sanitizer'
22
- autoload :Stats, 'honeybadger/util/stats'
23
- autoload :HTTP, 'honeybadger/util/http'
24
11
  end
25
12
  end
@@ -0,0 +1,50 @@
1
+ module Honeybadger
2
+ class ContextManager
3
+
4
+ def self.current
5
+ Thread.current[:__hb_context_manager] ||= new
6
+ end
7
+
8
+ def initialize
9
+ @mutex = Mutex.new
10
+ _initialize
11
+ end
12
+
13
+ def clear!
14
+ _initialize
15
+ end
16
+
17
+ # Internal accessors
18
+
19
+ def set_context(hash)
20
+ @mutex.synchronize do
21
+ @context ||= {}
22
+ @context.update(hash)
23
+ end
24
+ end
25
+
26
+ def get_context
27
+ @mutex.synchronize { @context }
28
+ end
29
+
30
+ def set_rack_env(env)
31
+ @mutex.synchronize { @rack_env = env }
32
+ end
33
+
34
+ def get_rack_env
35
+ @mutex.synchronize { @rack_env }
36
+ end
37
+
38
+ private
39
+
40
+ attr_accessor :custom, :rack_env
41
+
42
+ def _initialize
43
+ @mutex.synchronize do
44
+ @context = nil
45
+ @rack_env = nil
46
+ end
47
+ end
48
+
49
+ end
50
+ end
@@ -1,11 +1,7 @@
1
1
  require 'rails'
2
2
  require 'yaml'
3
3
 
4
- require 'honeybadger/util/sanitizer'
5
- require 'honeybadger/util/request_payload'
6
- require 'honeybadger/rack/error_notifier'
7
- require 'honeybadger/rack/user_informer'
8
- require 'honeybadger/rack/user_feedback'
4
+ require 'honeybadger/ruby'
9
5
 
10
6
  module Honeybadger
11
7
  module Init
@@ -15,29 +11,21 @@ module Honeybadger
15
11
  load 'honeybadger/tasks.rb'
16
12
  end
17
13
 
18
- initializer 'honeybadger.install' do
19
- config = Config.new(local_config)
20
- if Honeybadger.start(config)
21
- if config.feature?(:notices) && config[:'exceptions.enabled']
22
- ::Rails.application.config.middleware.tap do |middleware|
23
- middleware.insert(0, Honeybadger::Rack::ErrorNotifier, config)
24
- middleware.insert_before(Honeybadger::Rack::ErrorNotifier, Honeybadger::Rack::UserInformer, config) if config[:'user_informer.enabled']
25
- middleware.insert_before(Honeybadger::Rack::ErrorNotifier, Honeybadger::Rack::UserFeedback, config) if config[:'feedback.enabled']
26
- end
27
- end
28
- end
14
+ initializer 'honeybadger.install_middleware' do |app|
15
+ app.config.middleware.insert(0, Honeybadger::Rack::ErrorNotifier)
16
+ app.config.middleware.insert_before(Honeybadger::Rack::ErrorNotifier, Honeybadger::Rack::UserInformer)
17
+ app.config.middleware.insert_before(Honeybadger::Rack::ErrorNotifier, Honeybadger::Rack::UserFeedback)
29
18
  end
30
19
 
31
- private
32
-
33
- def local_config
34
- {
20
+ config.after_initialize do
21
+ Honeybadger.init!({
35
22
  :root => ::Rails.root.to_s,
36
23
  :env => ::Rails.env,
37
24
  :'config.path' => ::Rails.root.join('config', 'honeybadger.yml'),
38
25
  :logger => Logging::FormattedLogger.new(::Rails.logger),
39
26
  :framework => :rails
40
- }
27
+ })
28
+ Honeybadger.load_plugins!
41
29
  end
42
30
  end
43
31
  end
@@ -1,3 +1,5 @@
1
+ require 'honeybadger/ruby'
2
+
1
3
  # Patch Rake::Application to handle errors with Honeybadger
2
4
  module Honeybadger
3
5
  module RakeHandler
@@ -0,0 +1,9 @@
1
+ require 'honeybadger/ruby'
2
+
3
+ Honeybadger.init!({
4
+ :framework => :ruby,
5
+ :env => ENV['RUBY_ENV'] || ENV['RACK_ENV'],
6
+ :'logging.path' => 'STDOUT'
7
+ })
8
+
9
+ Honeybadger.load_plugins!
@@ -1,3 +1,6 @@
1
+ require 'sinatra/base'
2
+ require 'honeybadger/ruby'
3
+
1
4
  module Honeybadger
2
5
  module Init
3
6
  module Sinatra
@@ -12,22 +15,26 @@ module Honeybadger
12
15
 
13
16
  def honeybadger_config(app)
14
17
  {
15
- api_key: defined?(honeybadger_api_key) ? honeybadger_api_key : nil
18
+ api_key: defined?(honeybadger_api_key) ? honeybadger_api_key : nil,
19
+ env: ENV['APP_ENV'] || ENV['RACK_ENV'],
20
+ framework: :sinatra,
21
+ :'logging.path' => 'STDOUT'
16
22
  }
17
23
  end
18
24
 
19
25
  def install_honeybadger
20
- config = Honeybadger::Config.new(honeybadger_config(self))
26
+ Honeybadger.init!(honeybadger_config(self))
27
+ Honeybadger.load_plugins!
21
28
 
29
+ config = Honeybadger.config
22
30
  return unless config[:'sinatra.enabled']
23
- return unless Honeybadger.start(config)
24
31
 
25
- install_honeybadger_middleware(Honeybadger::Rack::ErrorNotifier, config) if config.feature?(:notices) && config[:'exceptions.enabled']
32
+ install_honeybadger_middleware(Honeybadger::Rack::ErrorNotifier) if config[:'exceptions.enabled']
26
33
  end
27
34
 
28
- def install_honeybadger_middleware(klass, config)
35
+ def install_honeybadger_middleware(klass)
29
36
  return if middleware.any? {|m| m[0] == klass }
30
- use(klass, config)
37
+ use(klass)
31
38
  end
32
39
  end
33
40
  end
@@ -6,6 +6,7 @@ require 'honeybadger/version'
6
6
  require 'honeybadger/backtrace'
7
7
  require 'honeybadger/util/stats'
8
8
  require 'honeybadger/util/sanitizer'
9
+ require 'honeybadger/util/request_hash'
9
10
  require 'honeybadger/util/request_payload'
10
11
 
11
12
  module Honeybadger
@@ -130,12 +131,13 @@ module Honeybadger
130
131
  @opts = opts
131
132
  @config = config
132
133
 
133
- @sanitizer = Util::Sanitizer.new
134
- @request_sanitizer = Util::Sanitizer.new(filters: config.params_filters)
134
+ @rack_env = opts.fetch(:rack_env, nil)
135
+
136
+ @request_sanitizer = Util::Sanitizer.new(filters: params_filters)
135
137
 
136
138
  @exception = unwrap_exception(opts[:exception])
137
- @error_class = exception_attribute(:error_class) {|exception| exception.class.name }
138
- @error_message = exception_attribute(:error_message, 'Notification') do |exception|
139
+ @error_class = exception_attribute(:error_class, 'Notice') {|exception| exception.class.name }
140
+ @error_message = exception_attribute(:error_message, 'No message provided') do |exception|
139
141
  "#{exception.class.name}: #{exception.message}"
140
142
  end
141
143
  @backtrace = parse_backtrace(exception_attribute(:backtrace, caller))
@@ -224,7 +226,8 @@ module Honeybadger
224
226
 
225
227
  private
226
228
 
227
- attr_reader :config, :opts, :context, :stats, :now, :pid, :causes, :sanitizer, :request_sanitizer
229
+ attr_reader :config, :opts, :context, :stats, :now, :pid, :causes,
230
+ :request_sanitizer, :rack_env
228
231
 
229
232
  def ignore_by_origin?
230
233
  return false if opts[:origin] != :rake
@@ -233,9 +236,8 @@ module Honeybadger
233
236
  end
234
237
 
235
238
  def ignore_by_callbacks?
236
- opts[:callbacks] &&
237
- opts[:callbacks].exception_filter &&
238
- opts[:callbacks].exception_filter.call(self)
239
+ config.exception_filter &&
240
+ config.exception_filter.call(self)
239
241
  end
240
242
 
241
243
  # Gets a property named "attribute" of an exception, either from
@@ -286,21 +288,26 @@ module Honeybadger
286
288
  end
287
289
  end
288
290
 
289
- ignored_class ? @ignore_by_class.call(ignored_class) : config[:'exceptions.ignore'].any?(&@ignore_by_class)
291
+ ignored_class ? @ignore_by_class.call(ignored_class) : config.ignored_classes.any?(&@ignore_by_class)
290
292
  end
291
293
 
292
294
  def construct_backtrace_filters(opts)
293
295
  [
294
- opts[:callbacks] ? opts[:callbacks].backtrace_filter : nil
296
+ config.backtrace_filter
295
297
  ].compact | BACKTRACE_FILTERS
296
298
  end
297
299
 
300
+ def request_hash
301
+ return {} unless rack_env
302
+ Util::RequestHash.from_env(rack_env)
303
+ end
304
+
298
305
  # Internal: Construct the request object with data from various sources.
299
306
  #
300
307
  # Returns Request.
301
308
  def construct_request_hash(config, opts)
302
309
  request = {}
303
- request.merge!(config.request_hash)
310
+ request.merge!(request_hash)
304
311
  request.merge!(opts)
305
312
  request[:component] = opts[:controller] if opts.has_key?(:controller)
306
313
  request[:params] = opts[:parameters] if opts.has_key?(:parameters)
@@ -311,14 +318,14 @@ module Honeybadger
311
318
 
312
319
  def construct_context_hash(opts)
313
320
  context = {}
314
- context.merge!(Thread.current[:__honeybadger_context]) if Thread.current[:__honeybadger_context]
321
+ context.merge!(opts[:global_context]) if opts[:global_context]
315
322
  context.merge!(opts[:context]) if opts[:context]
316
323
  context.empty? ? nil : context
317
324
  end
318
325
 
319
326
  def fingerprint_from_opts(opts)
320
327
  callback = opts[:fingerprint]
321
- callback ||= opts[:callbacks] && opts[:callbacks].exception_fingerprint
328
+ callback ||= config.exception_fingerprint
322
329
 
323
330
  if callback.respond_to?(:call)
324
331
  callback.call(self)
@@ -347,7 +354,7 @@ module Honeybadger
347
354
  end
348
355
 
349
356
  def s(data)
350
- sanitizer.sanitize(data)
357
+ Util::Sanitizer.sanitize(data)
351
358
  end
352
359
 
353
360
  # Internal: Fetch local variables from first frame of backtrace.
@@ -451,6 +458,14 @@ module Honeybadger
451
458
  c
452
459
  end
453
460
 
461
+ def params_filters
462
+ config.params_filters + rails_params_filters
463
+ end
464
+
465
+ def rails_params_filters
466
+ rack_env && Array(rack_env['action_dispatch.parameter_filter']) or []
467
+ end
468
+
454
469
  # Internal: This is how much Honeybadger cares about Rails developers. :)
455
470
  #
456
471
  # Some Rails projects include ActionDispatch::TestProcess globally for the