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
@@ -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