honeybadger 2.7.2 → 3.0.0.beta1

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