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
@@ -1,5 +1,11 @@
1
1
  require 'forwardable'
2
2
 
3
+ require 'honeybadger/backend/base'
4
+ require 'honeybadger/backend/server'
5
+ require 'honeybadger/backend/test'
6
+ require 'honeybadger/backend/null'
7
+ require 'honeybadger/backend/debug'
8
+
3
9
  module Honeybadger
4
10
  module Backend
5
11
  class BackendError < StandardError; end
@@ -16,11 +22,5 @@ module Honeybadger
16
22
  def self.for(backend)
17
23
  mapping[backend] or raise(BackendError, "Unable to locate backend: #{backend}")
18
24
  end
19
-
20
- autoload :Base, 'honeybadger/backend/base'
21
- autoload :Server, 'honeybadger/backend/server'
22
- autoload :Test, 'honeybadger/backend/test'
23
- autoload :Null, 'honeybadger/backend/null'
24
- autoload :Debug, 'honeybadger/backend/debug'
25
25
  end
26
26
  end
@@ -11,6 +11,13 @@ module Honeybadger
11
11
 
12
12
  attr_reader :code, :body, :message, :error
13
13
 
14
+ FRIENDLY_ERRORS = {
15
+ 429 => "Your project is currently sending too many errors.\nThis issue should resolve itself once error traffic is reduced.".freeze,
16
+ 503 => "Your project is currently sending too many errors.\nThis issue should resolve itself once error traffic is reduced.".freeze,
17
+ 402 => "The project owner's billing information has expired (or the trial has ended).\nPlease check your payment details or email support@honeybadger.io for help.".freeze,
18
+ 403 => "The API key is invalid. Please check your API key and try again.".freeze
19
+ }.freeze
20
+
14
21
  # Public: Initializes the Response instance.
15
22
  #
16
23
  # response - With 1 argument Net::HTTPResponse, the code, body, and
@@ -37,6 +44,10 @@ module Honeybadger
37
44
  @success
38
45
  end
39
46
 
47
+ def error_message
48
+ FRIENDLY_ERRORS[code] || message
49
+ end
50
+
40
51
  private
41
52
 
42
53
  def parse_error(body)
@@ -15,17 +15,7 @@ module Honeybadger
15
15
  deploys: '/v1/deploys'.freeze
16
16
  }.freeze
17
17
 
18
- HTTP_ERRORS = [Timeout::Error,
19
- Errno::EINVAL,
20
- Errno::ECONNRESET,
21
- Errno::ECONNREFUSED,
22
- Errno::ENETUNREACH,
23
- EOFError,
24
- Net::HTTPBadResponse,
25
- Net::HTTPHeaderSyntaxError,
26
- Net::ProtocolError,
27
- OpenSSL::SSL::SSLError,
28
- SocketError].freeze
18
+ HTTP_ERRORS = Util::HTTP::ERRORS
29
19
 
30
20
  def initialize(config)
31
21
  @http = Util::HTTP.new(config)
@@ -42,9 +32,7 @@ module Honeybadger
42
32
  ENDPOINTS[feature] or raise(BackendError, "Unknown feature: #{feature}")
43
33
  Response.new(@http.post(ENDPOINTS[feature], payload, payload_headers(payload)))
44
34
  rescue *HTTP_ERRORS => e
45
- Response.new(:error, nil, "HTTP Error: #{e.class}").tap do |response|
46
- error { sprintf('http error class=%s message=%s', e.class, e.message.dump) }
47
- end
35
+ Response.new(:error, nil, "HTTP Error: #{e.class}")
48
36
  end
49
37
 
50
38
  private
@@ -2,8 +2,6 @@ $:.unshift(File.expand_path('../../../vendor/cli', __FILE__))
2
2
 
3
3
  require 'thor'
4
4
 
5
- require 'honeybadger'
6
- require 'honeybadger/cli/heroku'
7
5
  require 'honeybadger/cli/main'
8
6
 
9
7
  module Honeybadger
@@ -0,0 +1,42 @@
1
+ require 'forwardable'
2
+ require 'honeybadger/cli/main'
3
+ require 'honeybadger/util/http'
4
+
5
+ module Honeybadger
6
+ module CLI
7
+ class Deploy
8
+ extend Forwardable
9
+
10
+ def initialize(options, args, config)
11
+ @options = options
12
+ @args = args
13
+ @config = config
14
+ @shell = ::Thor::Base.shell.new
15
+ end
16
+
17
+ def run
18
+ payload = {
19
+ environment: options['environment'],
20
+ revision: options['revision'],
21
+ repository: options['repository'],
22
+ local_username: options['user']
23
+ }
24
+
25
+ http = Util::HTTP.new(config)
26
+ result = http.post('/v1/deploys', payload)
27
+ if result.code == '201'
28
+ say("Deploy notification complete.", :green)
29
+ else
30
+ say("Invalid response from server: #{result.code}", :red)
31
+ exit(1)
32
+ end
33
+ end
34
+
35
+ private
36
+
37
+ attr_reader :options, :args, :config
38
+
39
+ def_delegator :@shell, :say
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,138 @@
1
+ require 'erb'
2
+ require 'forwardable'
3
+ require 'honeybadger/cli/main'
4
+ require 'honeybadger/util/http'
5
+ require 'honeybadger/util/stats'
6
+ require 'open3'
7
+ require 'ostruct'
8
+ require 'thor/shell'
9
+
10
+ module Honeybadger
11
+ module CLI
12
+ class Exec
13
+ extend Forwardable
14
+
15
+ FAILED_TEMPLATE = <<-MSG
16
+ Honeybadger detected failure or error output for the command:
17
+ `<%= args.join(' ') %>`
18
+
19
+ PROCESS ID: <%= pid %>
20
+
21
+ RESULT CODE: <%= code %>
22
+
23
+ ERROR OUTPUT:
24
+ <%= stderr %>
25
+
26
+ STANDARD OUTPUT:
27
+ <%= stdout %>
28
+ MSG
29
+
30
+ NO_EXEC_TEMPLATE = <<-MSG
31
+ Honeybadger failed to execute the following command:
32
+ `<%= args.join(' ') %>`
33
+
34
+ The command was not executable. Try adjusting permissions on the file.
35
+ MSG
36
+
37
+ NOT_FOUND_TEMPLATE = <<-MSG
38
+ Honeybadger failed to execute the following command:
39
+ `<%= args.join(' ') %>`
40
+
41
+ The command was not found. Make sure it exists in your PATH.
42
+ MSG
43
+
44
+ def initialize(options, args, config)
45
+ @options = options
46
+ @args = args
47
+ @config = config
48
+ @shell = ::Thor::Base.shell.new
49
+ end
50
+
51
+ def run
52
+ result = exec_cmd
53
+ return if result.success
54
+
55
+ payload = {
56
+ api_key: config.get(:api_key),
57
+ notifier: NOTIFIER,
58
+ error: {
59
+ class: 'honeybdager exec error',
60
+ message: result.msg
61
+ },
62
+ request: {
63
+ context: {
64
+ executable: args.first,
65
+ code: result.code,
66
+ pid: result.pid
67
+ }
68
+ },
69
+ server: {
70
+ project_root: Dir.pwd,
71
+ environment_name: config.get(:env),
72
+ time: Time.now,
73
+ stats: Util::Stats.all
74
+ }
75
+ }
76
+
77
+ http = Util::HTTP.new(config)
78
+
79
+ begin
80
+ response = http.post('/v1/notices', payload)
81
+ rescue
82
+ say(result.msg)
83
+ raise
84
+ end
85
+
86
+ if response.code != '201'
87
+ say(result.msg)
88
+ say("\nFailed to notify Honeybadger: #{response.code}", :red)
89
+ exit(1)
90
+ end
91
+
92
+ unless quiet?
93
+ say(result.msg)
94
+ say("\nSuccessfully notified Honeybadger")
95
+ end
96
+
97
+ exit(0)
98
+ end
99
+
100
+ private
101
+
102
+ attr_reader :options, :args, :config
103
+
104
+ def_delegator :@shell, :say
105
+
106
+ def quiet?
107
+ !!options[:quiet]
108
+ end
109
+
110
+ def exec_cmd
111
+ stdout, stderr, status = Open3.capture3(args.join(' '))
112
+
113
+ pid = status.pid
114
+ code = status.to_i
115
+ msg = ERB.new(FAILED_TEMPLATE).result(binding) unless status.success?
116
+
117
+ OpenStruct.new(
118
+ msg: msg,
119
+ pid: pid,
120
+ code: code,
121
+ stdout: stdout,
122
+ stderr: stderr,
123
+ success: status.success? && stderr =~ BLANK
124
+ )
125
+ rescue Errno::EACCES, Errno::ENOEXEC
126
+ OpenStruct.new(
127
+ msg: ERB.new(NO_EXEC_TEMPLATE).result(binding),
128
+ code: 126
129
+ )
130
+ rescue Errno::ENOENT
131
+ OpenStruct.new(
132
+ msg: ERB.new(NOT_FOUND_TEMPLATE).result(binding),
133
+ code: 127
134
+ )
135
+ end
136
+ end
137
+ end
138
+ end
@@ -1,10 +1,6 @@
1
- require 'honeybadger/cli/helpers'
2
-
3
1
  module Honeybadger
4
2
  module CLI
5
3
  class Heroku < Thor
6
- include Helpers
7
-
8
4
  class_option :app, aliases: :'-a', type: :string, default: nil, desc: 'Specify optional Heroku APP'
9
5
 
10
6
  desc 'install_deploy_notification', 'Install Heroku deploy notifications addon'
@@ -37,19 +33,10 @@ module Honeybadger
37
33
  def install(api_key)
38
34
  say("Installing Honeybadger #{VERSION} for Heroku")
39
35
 
40
- load_rails(verbose: true)
41
-
42
- ENV['HONEYBADGER_LOGGING_LEVEL'] = '2'
43
- ENV['HONEYBADGER_LOGGING_TTY_LEVEL'] = '0'
44
- ENV['HONEYBADGER_LOGGING_PATH'] = 'STDOUT'
45
- ENV['HONEYBADGER_REPORT_DATA'] = 'true'
46
-
47
- ENV['HONEYBADGER_API_KEY'] = api_key
48
-
49
36
  app = options[:app] || detect_heroku_app(false)
50
37
  say("Adding config HONEYBADGER_API_KEY=#{api_key} to Heroku.", :magenta)
51
38
  unless write_heroku_env({'HONEYBADGER_API_KEY' => api_key}, app)
52
- say('Unable to update heroku config. Do you need to specify an app name?', :red)
39
+ say('Unable to update heroku config. You may need to specify an app name with --app APP', :red)
53
40
  exit(1)
54
41
  end
55
42
 
@@ -61,14 +48,6 @@ module Honeybadger
61
48
  say("To install manually, try `honeybadger heroku install_deploy_notification#{app ? " -a #{app}" : ""} -k #{api_key} --environment ENVIRONMENT`", :yellow)
62
49
  end
63
50
 
64
- config = Config.new(rails_framework_opts)
65
- Honeybadger.start(config) unless load_rails_env(verbose: true)
66
- say('Sending test notice')
67
- unless Agent.instance && send_test(false)
68
- say("Honeybadger is installed, but failed to send a test notice. Try `HONEYBADGER_API_KEY=#{api_key} honeybadger test`.", :red)
69
- exit(1)
70
- end
71
-
72
51
  say("Installation complete. Happy 'badgering!", :green)
73
52
  end
74
53
 
@@ -0,0 +1,74 @@
1
+ require 'forwardable'
2
+ require 'honeybadger/cli/main'
3
+ require 'honeybadger/cli/test'
4
+ require 'pathname'
5
+
6
+ module Honeybadger
7
+ module CLI
8
+ class Install
9
+ extend Forwardable
10
+
11
+ def initialize(options, api_key)
12
+ @options = options
13
+ @api_key = api_key
14
+ @shell = ::Thor::Base.shell.new
15
+ end
16
+
17
+ def run
18
+ say("Installing Honeybadger #{VERSION}")
19
+
20
+ begin
21
+ require File.join(Dir.pwd, 'config', 'application.rb')
22
+ rescue LoadError
23
+ nil
24
+ end
25
+
26
+ root = defined?(Rails.root) ? Rails.root : Pathname.new(Dir.pwd)
27
+ config_root = defined?(Rails.root) ? root.join('config') : root
28
+ config_path = config_root.join('honeybadger.yml')
29
+
30
+ if config_path.exist?
31
+ say("You're already on Honeybadger, so you're all set.", :yellow)
32
+ else
33
+ say("Writing configuration to: #{config_path}", :yellow)
34
+
35
+ path = config_path
36
+
37
+ if path.exist?
38
+ say("The configuration file #{config_path} already exists.", :red)
39
+ exit(1)
40
+ elsif !path.dirname.writable?
41
+ say("The configuration path #{config_path.dirname} is not writable.", :red)
42
+ exit(1)
43
+ end
44
+
45
+ File.open(path, 'w+') do |file|
46
+ file.write(<<-CONFIG)
47
+ ---
48
+ api_key: '#{api_key}'
49
+ CONFIG
50
+ end
51
+ end
52
+
53
+ if (capfile = root.join('Capfile')).exist?
54
+ if capfile.read.match(/honeybadger/)
55
+ say("Detected Honeybadger in Capfile; skipping Capistrano installation.", :yellow)
56
+ else
57
+ say("Appending Capistrano tasks to: #{capfile}", :yellow)
58
+ File.open(capfile, 'a') do |f|
59
+ f.puts("\nrequire 'capistrano/honeybadger'")
60
+ end
61
+ end
62
+ end
63
+
64
+ Test.new({install: true}.freeze).run
65
+ end
66
+
67
+ private
68
+
69
+ attr_reader :options, :api_key
70
+
71
+ def_delegator :@shell, :say
72
+ end
73
+ end
74
+ end
@@ -1,195 +1,180 @@
1
- require 'stringio'
2
-
3
- require 'honeybadger/cli/helpers'
1
+ require 'honeybadger/cli/deploy'
2
+ require 'honeybadger/cli/exec'
3
+ require 'honeybadger/cli/heroku'
4
+ require 'honeybadger/cli/install'
5
+ require 'honeybadger/cli/notify'
6
+ require 'honeybadger/cli/test'
7
+ require 'honeybadger/config'
8
+ require 'honeybadger/config/defaults'
9
+ require 'honeybadger/util/http'
10
+ require 'honeybadger/version'
11
+ require 'logger'
4
12
 
5
13
  module Honeybadger
6
14
  module CLI
15
+ BLANK = /\A\s*\z/
16
+
17
+ NOTIFIER = {
18
+ name: 'honeybadger-ruby (cli)'.freeze,
19
+ url: 'https://github.com/honeybadger-io/honeybadger-ruby'.freeze,
20
+ version: VERSION,
21
+ language: nil
22
+ }.freeze
23
+
7
24
  class Main < Thor
8
- include Helpers
25
+ def self.project_options
26
+ option :api_key, required: false, aliases: :'-k', type: :string, desc: 'Api key of your Honeybadger application'
27
+ option :environment, required: false, aliases: [:'-e', :'-env'], type: :string, desc: 'Environment this command is being executed in (i.e. "production", "staging")'
28
+ end
9
29
 
10
- class HoneybadgerTestingException < RuntimeError; end
30
+ desc 'install API_KEY', 'Install Honeybadger into a new project'
31
+ def install(api_key)
32
+ Install.new(options, api_key).run
33
+ rescue => e
34
+ log_error(e)
35
+ exit(1)
36
+ end
11
37
 
12
- NOT_BLANK = Regexp.new('\S').freeze
38
+ desc 'test', 'Send a test notification from Honeybadger'
39
+ option :dry_run, type: :boolean, aliases: :'-d', default: false, desc: 'Skip sending data to Honeybadger'
40
+ option :file, type: :string, aliases: :'-f', default: nil, desc: 'Write the output to FILE'
41
+ def test
42
+ Test.new(options).run
43
+ rescue => e
44
+ log_error(e)
45
+ exit(1)
46
+ end
13
47
 
14
48
  desc 'deploy', 'Notify Honeybadger of deployment'
15
- option :environment, aliases: :'-e', type: :string, desc: 'Environment of the deploy (i.e. "production", "staging")'
16
- option :revision, aliases: :'-s', type: :string, desc: 'The revision/sha that is being deployed'
17
- option :repository, aliases: :'-r', type: :string, desc: 'The address of your repository'
18
- option :user, aliases: :'-u', type: :string, default: ENV['USER'] || ENV['USERNAME'], desc: 'The local user who is deploying'
19
- option :api_key, aliases: :'-k', type: :string, desc: 'Api key of your Honeybadger application'
49
+ project_options
50
+ option :repository, required: true, type: :string, aliases: :'-r', desc: 'The address of your repository'
51
+ option :revision, required: true, type: :string, aliases: :'-s', desc: 'The revision/sha that is being deployed'
52
+ option :user, required: true, type: :string, aliases: :'-u', default: ENV['USER'] || ENV['USERNAME'], desc: 'The local user who is deploying'
20
53
  def deploy
21
- load_rails(verbose: true)
54
+ config = build_config(options)
22
55
 
23
- payload = Hash[[:environment, :revision, :repository].map {|k| [k, options[k]] }]
24
- payload[:local_username] = options[:user]
25
-
26
- ENV['HONEYBADGER_LOGGING_LEVEL'] = '2'
27
- ENV['HONEYBADGER_LOGGING_TTY_LEVEL'] = '0'
28
- ENV['HONEYBADGER_LOGGING_PATH'] = 'STDOUT'
29
- ENV['HONEYBADGER_REPORT_DATA'] = 'true'
30
-
31
- say('Loading configuration')
32
- config = Config.new(rails_framework_opts)
33
- config.update(api_key: options[:api_key]) if options[:api_key] =~ NOT_BLANK
34
-
35
- unless (payload[:environment] ||= config[:env]) =~ NOT_BLANK
36
- say('Unable to determine environment. (see: `honeybadger help deploy`)', :red)
37
- exit(1)
56
+ if config.get(:api_key).to_s =~ BLANK
57
+ say("No value provided for required options '--api-key'")
58
+ return
38
59
  end
39
60
 
40
- unless config.valid?
41
- say("Invalid configuration: #{config.inspect}", :red)
42
- exit(1)
43
- end
61
+ Deploy.new(options, [], config).run
62
+ rescue => e
63
+ log_error(e)
64
+ exit(1)
65
+ end
44
66
 
45
- response = config.backend.notify(:deploys, payload)
46
- if response.success?
47
- say("Deploy notification for #{payload[:environment]} complete.", :green)
48
- else
49
- say("Deploy notification failed: #{response.code}", :red)
50
- exit(1)
67
+ desc 'notify', 'Notify Honeybadger of an error'
68
+ project_options
69
+ option :class, required: true, type: :string, aliases: :'-c', default: 'CLI Notification', desc: 'The class name of the error. (Default: CLI Notification)'
70
+ option :message, required: true, type: :string, aliases: :'-m', desc: 'The error message.'
71
+ option :action, required: false, type: :string, aliases: :'-a', desc: 'The action.'
72
+ option :component, required: false, type: :string, aliases: :'-C', desc: 'The component.'
73
+ option :fingerprint, required: false, type: :string, aliases: :'-f', desc: 'The component.'
74
+ option :tags, required: false, type: :string, aliases: :'-t', desc: 'The tags.'
75
+ option :url, required: false, type: :string, aliases: :'-u', desc: 'The URL.'
76
+ def notify
77
+ config = build_config(options)
78
+
79
+ if config.get(:api_key).to_s =~ BLANK
80
+ say("No value provided for required options '--api-key'")
81
+ return
51
82
  end
83
+
84
+ Notify.new(options, [], config).run
52
85
  rescue => e
53
- say("An error occurred during deploy notification: #{e}\n\t#{e.backtrace.join("\n\t")}", :red)
86
+ log_error(e)
54
87
  exit(1)
55
88
  end
56
89
 
57
- desc 'config', 'List configuration options'
58
- option :default, aliases: :'-d', type: :boolean, default: true, desc: 'Output default options'
59
- def config
60
- load_rails
61
- config = Config.new(rails_framework_opts)
62
- output_config(config.to_hash(options[:default]))
63
- end
90
+ desc 'exec', 'Execute a command. If the exit status is not 0, report the result to Honeybadger'
91
+ project_options
92
+ option :quiet, required: false, type: :boolean, aliases: :'-q', default: false, desc: 'Suppress all output unless Honeybdager notification fails.'
93
+ def exec(*args)
94
+ config = build_config(options)
64
95
 
65
- desc 'test', 'Output test/debug information'
66
- option :dry_run, aliases: :'-d', type: :boolean, default: false, desc: 'Skip sending data to Honeybadger'
67
- option :file, aliases: :'-f', type: :string, default: nil, desc: 'Write the output to FILE'
68
- def test
69
- if options[:file]
70
- out = StringIO.new
71
- $stdout = out
72
-
73
- flush = Proc.new do
74
- $stdout = STDOUT
75
- File.open(options[:file], 'w+') do |f|
76
- out.rewind
77
- out.each_line {|l| f.write(l) }
78
- end
79
-
80
- say("Output written to #{options[:file]}", :green)
81
- end
82
-
83
- Agent.at_exit(&flush)
84
-
85
- at_exit do
86
- # If the agent couldn't be started, the callback should happen here
87
- # instead.
88
- flush.() unless Agent.running?
89
- end
96
+ if config.get(:api_key).to_s =~ BLANK
97
+ say("No value provided for required options '--api-key'")
98
+ return
90
99
  end
91
100
 
92
- say("Detecting framework\n\n", :bold)
93
- load_rails(verbose: true)
94
-
95
- ENV['HONEYBADGER_LOGGING_LEVEL'] = '0'
96
- ENV['HONEYBADGER_LOGGING_TTY_LEVEL'] = '0'
97
- ENV['HONEYBADGER_LOGGING_PATH'] = 'STDOUT'
98
- ENV['HONEYBADGER_DEBUG'] = 'true'
99
- ENV['HONEYBADGER_REPORT_DATA'] = options[:dry_run] ? 'false' : 'true'
101
+ Exec.new(options, args, config).run
102
+ rescue => e
103
+ log_error(e)
104
+ exit(1)
105
+ end
100
106
 
101
- config = Config.new(rails_framework_opts)
102
- say("\nConfiguration\n\n", :bold)
103
- output_config(config.to_hash)
107
+ desc 'heroku SUBCOMMAND ...ARGS', 'Manage Honeybadger on Heroku'
108
+ subcommand 'heroku', Heroku
104
109
 
105
- say("\nStarting Honeybadger\n\n", :bold)
106
- Honeybadger.start(config) unless load_rails_env(verbose: true)
110
+ private
107
111
 
108
- say("\nSending test notice\n\n", :bold)
109
- send_test
112
+ def fetch_value(options, key)
113
+ options[key] == key ? nil : options[key]
114
+ end
110
115
 
111
- say("\nRunning at exit hooks\n\n", :bold)
116
+ def build_config(options)
117
+ config = Config.new(logger: Logger.new('/dev/null'))
118
+ config.set(:api_key, fetch_value(options, 'api_key')) if options.has_key?('api_key')
119
+ config.set(:env, fetch_value(options, 'environment')) if options.has_key?('environment')
120
+ config.init!({
121
+ framework: :cli
122
+ })
123
+ config
112
124
  end
113
125
 
114
- desc 'install API_KEY', 'Install Honeybadger into the current directory using API_KEY'
115
- option :test, aliases: :'-t', type: :boolean, default: nil, desc: 'Send a test error'
116
- def install(api_key)
117
- say("Installing Honeybadger #{VERSION}")
126
+ def log_error(e)
127
+ case e
128
+ when *Util::HTTP::ERRORS
129
+ say(<<-MSG, :red)
130
+ !! --- Failed to notify Honeybadger ------------------------------------------- !!
131
+
132
+ # What happened?
133
+
134
+ We encountered an HTTP error while contacting our service. Issues like this are
135
+ usually temporary.
136
+
137
+ # Error details
118
138
 
119
- load_rails(verbose: true)
139
+ #{e.class}: #{e.message}\n at #{e.backtrace && e.backtrace.first}
120
140
 
121
- ENV['HONEYBADGER_LOGGING_LEVEL'] = '2'
122
- ENV['HONEYBADGER_LOGGING_TTY_LEVEL'] = '0'
123
- ENV['HONEYBADGER_LOGGING_PATH'] = 'STDOUT'
124
- ENV['HONEYBADGER_REPORT_DATA'] = 'true'
141
+ # What can I do?
125
142
 
126
- config = Config.new(rails_framework_opts)
127
- config[:api_key] = api_key.to_s
143
+ - Retry the command.
144
+ - Make sure you can connect to api.honeybadger.io (`ping api.honeybadger.io`).
145
+ - If you continue to see this message, email us at support@honeybadger.io
146
+ (don't forget to attach this output!)
128
147
 
129
- if (path = config.config_path).exist?
130
- say("You're already on Honeybadger, so you're all set.", :yellow)
148
+ !! --- End -------------------------------------------------------------------- !!
149
+ MSG
131
150
  else
132
- say("Writing configuration to: #{path}", :yellow)
133
-
134
- begin
135
- config.write
136
- rescue Config::ConfigError => e
137
- error("Error: Unable to write configuration file:\n\t#{e}")
138
- return
139
- rescue StandardError => e
140
- error("Error: Unable to write configuration file:\n\t#{e.class} -- #{e.message}\n\t#{e.backtrace.join("\n\t")}")
141
- return
142
- end
143
- end
151
+ say(<<-MSG, :red)
152
+ !! --- Honeybadger command failed --------------------------------------------- !!
144
153
 
145
- if (capfile = Pathname.new(config[:root]).join('Capfile')).exist?
146
- if capfile.read.match(/honeybadger/)
147
- say("Detected Honeybadger in Capfile; skipping Capistrano installation.", :yellow)
148
- else
149
- say("Appending Capistrano tasks to: #{capfile}", :yellow)
150
- File.open(capfile, 'a') do |f|
151
- f.puts("\nrequire 'capistrano/honeybadger'")
152
- end
153
- end
154
- end
154
+ # What did you try to do?
155
155
 
156
- if options[:test].nil? || options[:test]
157
- Honeybadger.start(config) unless load_rails_env(verbose: true)
158
- say('Sending test notice', :yellow)
159
- unless Agent.instance && send_test(false)
160
- say('Honeybadger is installed, but failed to send a test notice. Try `honeybadger test`.', :red)
161
- exit(1)
162
- end
163
- end
156
+ You tried to execute the following command:
157
+ `honeybadger #{ARGV.join(' ')}`
164
158
 
165
- say("Installation complete. Happy 'badgering!", :green)
166
- end
159
+ # What actually happend?
167
160
 
168
- desc 'heroku SUBCOMMAND ...ARGS', 'Manage Honeybadger on Heroku'
169
- subcommand 'heroku', Heroku
161
+ We encountered a Ruby exception and were forced to cancel your request.
170
162
 
171
- private
163
+ # Error details
172
164
 
173
- def output_config(nested_hash, hierarchy = [])
174
- nested_hash.each_pair do |key, value|
175
- if value.kind_of?(Hash)
176
- say(tab_indent(hierarchy.size) << "#{key}:")
177
- output_config(value, hierarchy + [key])
178
- else
179
- dotted_key = (hierarchy + [key]).join('.').to_sym
180
- say(tab_indent(hierarchy.size) << "#{key}:")
181
- indent = tab_indent(hierarchy.size+1)
182
- say(indent + "Description: #{Config::OPTIONS[dotted_key][:description]}")
183
- say(indent + "Type: #{Config::OPTIONS[dotted_key].fetch(:type, String).name.split('::').last}")
184
- say(indent + "Default: #{Config::OPTIONS[dotted_key][:default].inspect}")
185
- say(indent + "Current: #{value.inspect}")
186
- end
187
- end
188
- end
165
+ #{e.class}: #{e.message}
166
+ #{e.backtrace && e.backtrace.join("\n ")}
167
+
168
+ # What can I do?
169
+
170
+ - If you're calling the `install` or `test` command in a Rails app, make sure
171
+ you can boot the Rails console: `bundle exec rails console`.
172
+ - Retry the command.
173
+ - If you continue to see this message, email us at support@honeybadger.io
174
+ (don't forget to attach this output!)
189
175
 
190
- def tab_indent(number)
191
- ''.tap do |s|
192
- number.times { s << "\s\s" }
176
+ !! --- End -------------------------------------------------------------------- !!
177
+ MSG
193
178
  end
194
179
  end
195
180
  end