honeybadger 1.16.7 → 2.0.0.beta.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +19 -0
- data/README.md +37 -16
- data/bin/honeybadger +5 -0
- data/lib/honeybadger.rb +167 -191
- data/lib/honeybadger/agent.rb +136 -0
- data/lib/honeybadger/backend.rb +26 -0
- data/lib/honeybadger/backend/base.rb +66 -0
- data/lib/honeybadger/backend/debug.rb +12 -0
- data/lib/honeybadger/backend/null.rb +16 -0
- data/lib/honeybadger/backend/server.rb +51 -0
- data/lib/honeybadger/backend/test.rb +24 -0
- data/lib/honeybadger/backtrace.rb +29 -24
- data/lib/honeybadger/cli.rb +367 -0
- data/lib/honeybadger/config.rb +333 -0
- data/lib/honeybadger/config/callbacks.rb +70 -0
- data/lib/honeybadger/config/defaults.rb +175 -0
- data/lib/honeybadger/config/env.rb +40 -0
- data/lib/honeybadger/config/yaml.rb +43 -0
- data/lib/honeybadger/const.rb +28 -0
- data/lib/honeybadger/init/rails.rb +84 -0
- data/lib/honeybadger/init/sinatra.rb +27 -0
- data/lib/honeybadger/logging.rb +133 -0
- data/lib/honeybadger/notice.rb +243 -280
- data/lib/honeybadger/plugin.rb +110 -0
- data/lib/honeybadger/plugins/delayed_job.rb +22 -0
- data/lib/honeybadger/{integrations → plugins}/delayed_job/plugin.rb +6 -7
- data/lib/honeybadger/{integrations → plugins}/local_variables.rb +7 -8
- data/lib/honeybadger/{integrations → plugins}/net_http.rb +10 -8
- data/lib/honeybadger/plugins/passenger.rb +24 -0
- data/lib/honeybadger/plugins/rails.rb +61 -0
- data/lib/honeybadger/plugins/sidekiq.rb +35 -0
- data/lib/honeybadger/{integrations → plugins}/thor.rb +9 -8
- data/lib/honeybadger/{integrations → plugins}/unicorn.rb +10 -9
- data/lib/honeybadger/rack/error_notifier.rb +44 -27
- data/lib/honeybadger/rack/metrics_reporter.rb +41 -0
- data/lib/honeybadger/rack/request_hash.rb +50 -0
- data/lib/honeybadger/rack/user_feedback.rb +15 -10
- data/lib/honeybadger/rack/user_informer.rb +14 -3
- data/lib/honeybadger/trace.rb +185 -0
- data/lib/honeybadger/util/http.rb +79 -0
- data/lib/honeybadger/util/request_sanitizer.rb +35 -0
- data/lib/honeybadger/util/sanitizer.rb +71 -0
- data/lib/honeybadger/util/stats.rb +31 -0
- data/lib/honeybadger/version.rb +4 -0
- data/lib/honeybadger/worker.rb +224 -0
- data/lib/honeybadger/worker/batch.rb +50 -0
- data/lib/honeybadger/worker/metered_queue.rb +80 -0
- data/lib/honeybadger/worker/metrics_collection.rb +61 -0
- data/lib/honeybadger/worker/metrics_collector.rb +96 -0
- data/{lib/honeybadger/capistrano.rb → vendor/capistrano-honeybadger/lib/capistrano/honeybadger.rb} +1 -3
- data/vendor/capistrano-honeybadger/lib/capistrano/tasks/deploy.cap +76 -0
- data/vendor/capistrano-honeybadger/lib/honeybadger/capistrano.rb +2 -0
- data/{lib → vendor/capistrano-honeybadger/lib}/honeybadger/capistrano/legacy.rb +16 -15
- data/vendor/thor/lib/thor.rb +484 -0
- data/vendor/thor/lib/thor/actions.rb +319 -0
- data/vendor/thor/lib/thor/actions/create_file.rb +103 -0
- data/vendor/thor/lib/thor/actions/create_link.rb +59 -0
- data/vendor/thor/lib/thor/actions/directory.rb +118 -0
- data/vendor/thor/lib/thor/actions/empty_directory.rb +135 -0
- data/vendor/thor/lib/thor/actions/file_manipulation.rb +316 -0
- data/vendor/thor/lib/thor/actions/inject_into_file.rb +107 -0
- data/vendor/thor/lib/thor/base.rb +656 -0
- data/vendor/thor/lib/thor/command.rb +133 -0
- data/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb +77 -0
- data/vendor/thor/lib/thor/core_ext/io_binary_read.rb +10 -0
- data/vendor/thor/lib/thor/core_ext/ordered_hash.rb +98 -0
- data/vendor/thor/lib/thor/error.rb +32 -0
- data/vendor/thor/lib/thor/group.rb +281 -0
- data/vendor/thor/lib/thor/invocation.rb +178 -0
- data/vendor/thor/lib/thor/line_editor.rb +17 -0
- data/vendor/thor/lib/thor/line_editor/basic.rb +35 -0
- data/vendor/thor/lib/thor/line_editor/readline.rb +88 -0
- data/vendor/thor/lib/thor/parser.rb +4 -0
- data/vendor/thor/lib/thor/parser/argument.rb +73 -0
- data/vendor/thor/lib/thor/parser/arguments.rb +175 -0
- data/vendor/thor/lib/thor/parser/option.rb +125 -0
- data/vendor/thor/lib/thor/parser/options.rb +218 -0
- data/vendor/thor/lib/thor/rake_compat.rb +71 -0
- data/vendor/thor/lib/thor/runner.rb +322 -0
- data/vendor/thor/lib/thor/shell.rb +81 -0
- data/vendor/thor/lib/thor/shell/basic.rb +421 -0
- data/vendor/thor/lib/thor/shell/color.rb +149 -0
- data/vendor/thor/lib/thor/shell/html.rb +126 -0
- data/vendor/thor/lib/thor/util.rb +267 -0
- data/vendor/thor/lib/thor/version.rb +3 -0
- metadata +97 -305
- data/Appraisals +0 -95
- data/CHANGELOG.md +0 -422
- data/Gemfile +0 -8
- data/Gemfile.lock +0 -136
- data/Guardfile +0 -5
- data/MIT-LICENSE +0 -32
- data/Rakefile +0 -159
- data/features/metal.feature +0 -20
- data/features/rack.feature +0 -55
- data/features/rails.feature +0 -343
- data/features/rails3.x.feature +0 -26
- data/features/rake.feature +0 -25
- data/features/sinatra.feature +0 -27
- data/features/standalone.feature +0 -73
- data/features/step_definitions/metal_steps.rb +0 -24
- data/features/step_definitions/rack_steps.rb +0 -18
- data/features/step_definitions/rails_steps.rb +0 -270
- data/features/step_definitions/rake_steps.rb +0 -17
- data/features/step_definitions/standalone_steps.rb +0 -12
- data/features/step_definitions/thor_steps.rb +0 -4
- data/features/support/env.rb +0 -22
- data/features/support/honeybadger_failure_shim.rb.template +0 -5
- data/features/support/honeybadger_shim.rb.template +0 -6
- data/features/support/rails.rb +0 -202
- data/features/support/rake/Rakefile +0 -68
- data/features/support/test.thor +0 -22
- data/features/thor.feature +0 -5
- data/gemfiles/binding_of_caller.gemfile +0 -13
- data/gemfiles/delayed_job.gemfile +0 -13
- data/gemfiles/rack.gemfile +0 -13
- data/gemfiles/rails.gemfile +0 -16
- data/gemfiles/rails2.3.gemfile +0 -15
- data/gemfiles/rails3.0.gemfile +0 -16
- data/gemfiles/rails3.1.gemfile +0 -16
- data/gemfiles/rails3.2.gemfile +0 -16
- data/gemfiles/rails4.0.gemfile +0 -16
- data/gemfiles/rails4.1.gemfile +0 -16
- data/gemfiles/rake.gemfile +0 -13
- data/gemfiles/sinatra.gemfile +0 -13
- data/gemfiles/standalone.gemfile +0 -12
- data/gemfiles/thor.gemfile +0 -13
- data/generators/honeybadger/honeybadger_generator.rb +0 -95
- data/generators/honeybadger/lib/insert_commands.rb +0 -34
- data/generators/honeybadger/lib/rake_commands.rb +0 -24
- data/generators/honeybadger/templates/capistrano_hook.rb +0 -6
- data/generators/honeybadger/templates/honeybadger_tasks.rake +0 -25
- data/generators/honeybadger/templates/initializer.rb +0 -6
- data/honeybadger.gemspec +0 -174
- data/lib/honeybadger/array.rb +0 -53
- data/lib/honeybadger/capistrano/tasks.rake +0 -73
- data/lib/honeybadger/configuration.rb +0 -397
- data/lib/honeybadger/dependency.rb +0 -65
- data/lib/honeybadger/integrations.rb +0 -9
- data/lib/honeybadger/integrations/delayed_job.rb +0 -20
- data/lib/honeybadger/integrations/passenger.rb +0 -18
- data/lib/honeybadger/integrations/sidekiq.rb +0 -37
- data/lib/honeybadger/monitor.rb +0 -17
- data/lib/honeybadger/monitor/railtie.rb +0 -53
- data/lib/honeybadger/monitor/sender.rb +0 -44
- data/lib/honeybadger/monitor/trace.rb +0 -187
- data/lib/honeybadger/monitor/worker.rb +0 -169
- data/lib/honeybadger/payload.rb +0 -101
- data/lib/honeybadger/rack.rb +0 -12
- data/lib/honeybadger/rails.rb +0 -45
- data/lib/honeybadger/rails/action_controller_catcher.rb +0 -30
- data/lib/honeybadger/rails/controller_methods.rb +0 -78
- data/lib/honeybadger/rails/middleware/exceptions_catcher.rb +0 -29
- data/lib/honeybadger/rails3_tasks.rb +0 -94
- data/lib/honeybadger/railtie.rb +0 -52
- data/lib/honeybadger/rake_handler.rb +0 -66
- data/lib/honeybadger/sender.rb +0 -185
- data/lib/honeybadger/shared_tasks.rb +0 -56
- data/lib/honeybadger/stats.rb +0 -29
- data/lib/honeybadger/tasks.rb +0 -95
- data/lib/honeybadger/user_feedback.rb +0 -8
- data/lib/honeybadger/user_informer.rb +0 -8
- data/lib/honeybadger_tasks.rb +0 -69
- data/lib/rails/generators/honeybadger/honeybadger_generator.rb +0 -99
- data/rails/init.rb +0 -1
- data/resources/README.md +0 -34
- data/script/integration_test.rb +0 -38
- data/spec/allocation_stats.rb +0 -32
- data/spec/honeybadger/backtrace_spec.rb +0 -242
- data/spec/honeybadger/capistrano_spec.rb +0 -36
- data/spec/honeybadger/configuration_spec.rb +0 -328
- data/spec/honeybadger/dependency_spec.rb +0 -134
- data/spec/honeybadger/integrations/delayed_job_spec.rb +0 -82
- data/spec/honeybadger/integrations/local_variables_spec.rb +0 -60
- data/spec/honeybadger/integrations/net_http_spec.rb +0 -29
- data/spec/honeybadger/integrations/passenger_spec.rb +0 -29
- data/spec/honeybadger/integrations/sidekiq_spec.rb +0 -60
- data/spec/honeybadger/integrations/thor_spec.rb +0 -32
- data/spec/honeybadger/integrations/unicorn_spec.rb +0 -40
- data/spec/honeybadger/logger_spec.rb +0 -79
- data/spec/honeybadger/monitor/trace_spec.rb +0 -65
- data/spec/honeybadger/monitor/worker_spec.rb +0 -274
- data/spec/honeybadger/notice_spec.rb +0 -669
- data/spec/honeybadger/notifier_spec.rb +0 -328
- data/spec/honeybadger/payload_spec.rb +0 -162
- data/spec/honeybadger/rack_spec.rb +0 -85
- data/spec/honeybadger/rails/action_controller_spec.rb +0 -328
- data/spec/honeybadger/rails_spec.rb +0 -37
- data/spec/honeybadger/sender_spec.rb +0 -317
- data/spec/honeybadger/stats_spec.rb +0 -57
- data/spec/honeybadger/user_feedback_spec.rb +0 -80
- data/spec/honeybadger/user_informer_spec.rb +0 -30
- data/spec/honeybadger_tasks_spec.rb +0 -171
- data/spec/spec_helper.rb +0 -24
- data/spec/support/array_including.rb +0 -31
- data/spec/support/backtraced_exception.rb +0 -9
- data/spec/support/collected_sender.rb +0 -12
- data/spec/support/defines_constants.rb +0 -18
- data/spec/support/helpers.rb +0 -101
@@ -0,0 +1,367 @@
|
|
1
|
+
$:.unshift(File.expand_path('../../../vendor/thor/lib', __FILE__))
|
2
|
+
|
3
|
+
require 'thor'
|
4
|
+
require 'honeybadger'
|
5
|
+
require 'stringio'
|
6
|
+
require 'logger'
|
7
|
+
|
8
|
+
module Honeybadger
|
9
|
+
class CLI < Thor
|
10
|
+
class HoneybadgerTestingException < RuntimeError; end
|
11
|
+
|
12
|
+
NOT_BLANK = Regexp.new('\S').freeze
|
13
|
+
|
14
|
+
class_option :platform, aliases: :'-p', type: :string, default: nil, desc: 'Specify optional PLATFORM (e.g. "heroku")'
|
15
|
+
class_option :app, aliases: :'-a', type: :string, default: nil, desc: 'Specify optional APP with PLATFORM'
|
16
|
+
|
17
|
+
desc 'deploy', 'Notify Honeybadger of deployment'
|
18
|
+
option :environment, aliases: [:'-e', :'--env'], type: :string, desc: 'Environment of the deploy (i.e. "production", "staging")'
|
19
|
+
option :revision, aliases: [:'-r', :'--rev', :'--sha'], type: :string, desc: 'The revision/sha that is being deployed'
|
20
|
+
option :repository, aliases: :'--repo', type: :string, desc: 'The address of your repository'
|
21
|
+
option :local_username, aliases: [:'--user', :'-u'], type: :string, default: ENV['USER'] || ENV['USERNAME'], desc: 'The local user who is deploying'
|
22
|
+
option :api_key, aliases: [:'-k', :'--key'], type: :string, desc: 'Api key of your Honeybadger application'
|
23
|
+
def deploy
|
24
|
+
load_rails(verbose: true)
|
25
|
+
exit(1) unless !options[:platform] || load_platform(options[:platform], options[:app])
|
26
|
+
|
27
|
+
payload = Hash[[:environment, :revision, :repository, :local_username].map {|k| [k, options[k]] }]
|
28
|
+
|
29
|
+
say('Loading configuration')
|
30
|
+
config = Config.new(rails_framework_opts)
|
31
|
+
config.update(api_key: options[:api_key]) if options[:api_key] =~ NOT_BLANK
|
32
|
+
|
33
|
+
unless (payload[:environment] ||= config[:env]) =~ NOT_BLANK
|
34
|
+
say('Unable to determine environment. (see: `honeybadger help deploy`)', :red)
|
35
|
+
exit(1)
|
36
|
+
end
|
37
|
+
|
38
|
+
unless config.valid?
|
39
|
+
say("Invalid configuration: #{config.inspect}", :red)
|
40
|
+
exit(1)
|
41
|
+
end
|
42
|
+
|
43
|
+
response = config.backend.notify(:deploys, payload)
|
44
|
+
if response.success?
|
45
|
+
say("Deploy notification for #{payload[:environment]} complete.", :green)
|
46
|
+
else
|
47
|
+
say("Deploy notification failed: #{response.code}", :red)
|
48
|
+
end
|
49
|
+
rescue => e
|
50
|
+
say("An error occurred during deploy notification: #{e}\n\t#{e.backtrace.join("\n\t")}", :red)
|
51
|
+
exit(1)
|
52
|
+
end
|
53
|
+
|
54
|
+
desc 'config', 'List configuration options'
|
55
|
+
option :default, aliases: :'-d', type: :boolean, default: true, desc: 'Output default options'
|
56
|
+
def config
|
57
|
+
exit(1) unless !options[:platform] || load_platform(options[:platform], options[:app])
|
58
|
+
load_rails
|
59
|
+
config = Config.new(rails_framework_opts)
|
60
|
+
output_config(config.to_hash(options[:default]))
|
61
|
+
end
|
62
|
+
|
63
|
+
desc 'debug', 'Output debug information'
|
64
|
+
option :test, aliases: :'-t', type: :boolean, default: false, desc: 'Send a test error'
|
65
|
+
option :file, aliases: :'-f', type: :string, default: nil, desc: 'Write the output to FILE.'
|
66
|
+
def debug
|
67
|
+
if options[:file]
|
68
|
+
out = StringIO.new
|
69
|
+
$stdout = out
|
70
|
+
|
71
|
+
Agent.at_exit do
|
72
|
+
$stdout = STDOUT
|
73
|
+
File.open(options[:file], 'w+') do |f|
|
74
|
+
out.rewind
|
75
|
+
out.each_line {|l| f.write(l) }
|
76
|
+
end
|
77
|
+
|
78
|
+
say("Output written to #{options[:file]}", :green)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
ENV['HONEYBADGER_LOGGING_PATH'] ||= 'STDOUT'
|
83
|
+
ENV['HONEYBADGER_LOGGING_LEVEL'] ||= '0'
|
84
|
+
ENV['HONEYBADGER_PUBLIC'] ||= 'true'
|
85
|
+
|
86
|
+
exit(1) unless !options[:platform] || load_platform(options[:platform], options[:app])
|
87
|
+
say("\n") if options[:platform] # Print a blank line if we just logged the platform.
|
88
|
+
|
89
|
+
say("Detecting framework\n\n", :bold)
|
90
|
+
load_rails(verbose: true)
|
91
|
+
|
92
|
+
config = Config.new(rails_framework_opts)
|
93
|
+
say("\nConfiguration\n\n", :bold)
|
94
|
+
output_config(config.to_hash)
|
95
|
+
|
96
|
+
if options[:test]
|
97
|
+
Honeybadger.start(config) unless load_rails_env(verbose: true)
|
98
|
+
say("\nSending test notice\n\n", :bold)
|
99
|
+
send_test
|
100
|
+
end
|
101
|
+
say("\nRunning at exit hooks\n\n", :bold)
|
102
|
+
end
|
103
|
+
|
104
|
+
desc 'install API_KEY', 'Install Honeybadger into the current directory using API_KEY'
|
105
|
+
option :test, aliases: :'-t', type: :boolean, default: nil, desc: 'Send a test error'
|
106
|
+
def install(api_key)
|
107
|
+
say("Installing Honeybadger #{VERSION}")
|
108
|
+
|
109
|
+
ENV['HONEYBADGER_LOGGING_PATH'] ||= 'STDOUT'
|
110
|
+
ENV['HONEYBADGER_LOGGING_LEVEL'] ||= '2'
|
111
|
+
ENV['HONEYBADGER_PUBLIC'] ||= 'true'
|
112
|
+
|
113
|
+
exit(1) unless !options[:platform] || load_platform(options[:platform], options[:app])
|
114
|
+
|
115
|
+
load_rails(verbose: true)
|
116
|
+
|
117
|
+
config = Config.new(rails_framework_opts)
|
118
|
+
config[:api_key] = api_key
|
119
|
+
|
120
|
+
if options[:platform]
|
121
|
+
if options[:platform] == 'heroku'
|
122
|
+
say("Adding config HONEYBADGER_API_KEY=#{api_key} to heroku.", :magenta)
|
123
|
+
unless write_heroku_env({'HONEYBADGER_API_KEY' => api_key}, options[:app])
|
124
|
+
say('Unable to update heroku config. Do you need to specify an app name?', :red)
|
125
|
+
return
|
126
|
+
end
|
127
|
+
end
|
128
|
+
elsif (path = config.config_path).exist?
|
129
|
+
say("You're already on Honeybadger, so you're all set.", :yellow)
|
130
|
+
skip_test = true if options[:test].nil? # Only if it wasn't specified.
|
131
|
+
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
|
144
|
+
|
145
|
+
if !skip_test && (options[:test].nil? || options[:test])
|
146
|
+
Honeybadger.start(config) unless load_rails_env(verbose: true)
|
147
|
+
say('Sending test notice', :yellow)
|
148
|
+
unless Agent.instance && send_test(false)
|
149
|
+
say('Honeybadger is installed, but failed to send a test notice. Try `honeybadger debug --test`.', :red)
|
150
|
+
return
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
say("Installation complete. Happy 'badgering!", :green)
|
155
|
+
end
|
156
|
+
|
157
|
+
private
|
158
|
+
|
159
|
+
def rails?(opts = {})
|
160
|
+
@rails ||= load_rails(opts)
|
161
|
+
end
|
162
|
+
|
163
|
+
def load_rails(opts = {})
|
164
|
+
begin
|
165
|
+
require 'honeybadger/init/rails'
|
166
|
+
if ::Rails::VERSION::MAJOR >= 3
|
167
|
+
say("Detected Rails #{::Rails::VERSION::STRING}") if opts[:verbose]
|
168
|
+
else
|
169
|
+
say("Error: Rails #{::Rails::VERSION::STRING} is unsupported.", :red)
|
170
|
+
exit(1)
|
171
|
+
end
|
172
|
+
rescue LoadError
|
173
|
+
say("Rails was not detected, loading standalone.") if opts[:verbose]
|
174
|
+
return @rails = false
|
175
|
+
rescue StandardError => e
|
176
|
+
say("Error while detecting Rails: #{e.class} -- #{e.message}", :red)
|
177
|
+
exit(1)
|
178
|
+
end
|
179
|
+
|
180
|
+
begin
|
181
|
+
require File.expand_path('config/application')
|
182
|
+
rescue LoadError
|
183
|
+
say('Error: could not load Rails application. Please ensure you run this command from your project root.', :red)
|
184
|
+
exit(1)
|
185
|
+
end
|
186
|
+
|
187
|
+
@rails = true
|
188
|
+
end
|
189
|
+
|
190
|
+
def load_rails_env(opts = {})
|
191
|
+
return false unless rails?(opts)
|
192
|
+
|
193
|
+
puts('Loading Rails environment') if opts[:verbose]
|
194
|
+
begin
|
195
|
+
require File.expand_path('config/environment')
|
196
|
+
rescue LoadError
|
197
|
+
say('Error: could not load Rails environment. Please ensure you run this command from your project root.', :red)
|
198
|
+
exit(1)
|
199
|
+
end
|
200
|
+
|
201
|
+
true
|
202
|
+
end
|
203
|
+
|
204
|
+
def rails_framework_opts
|
205
|
+
return {} unless defined?(::Rails)
|
206
|
+
|
207
|
+
{
|
208
|
+
:root => ::Rails.root,
|
209
|
+
:env => ::Rails.env,
|
210
|
+
:'config.path' => ::Rails.root.join('config', 'honeybadger.yml'),
|
211
|
+
:framework_name => "Rails #{::Rails::VERSION::STRING}",
|
212
|
+
:api_key => rails_secrets_api_key
|
213
|
+
}
|
214
|
+
end
|
215
|
+
|
216
|
+
def rails_secrets_api_key
|
217
|
+
if defined?(::Rails.application.secrets)
|
218
|
+
::Rails.application.secrets.honeybadger_api_key
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def output_config(nested_hash, hierarchy = [])
|
223
|
+
nested_hash.each_pair do |key, value|
|
224
|
+
if value.kind_of?(Hash)
|
225
|
+
say(tab_indent(hierarchy.size) << "#{key}:")
|
226
|
+
output_config(value, hierarchy + [key])
|
227
|
+
else
|
228
|
+
dotted_key = (hierarchy + [key]).join('.').to_sym
|
229
|
+
say(tab_indent(hierarchy.size) << "#{key}:")
|
230
|
+
indent = tab_indent(hierarchy.size+1)
|
231
|
+
say(indent + "Description: #{Config::OPTIONS[dotted_key][:description]}")
|
232
|
+
say(indent + "Default: #{Config::OPTIONS[dotted_key][:default].to_s}")
|
233
|
+
say(indent + "Current: #{value.to_s}")
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def tab_indent(number)
|
239
|
+
''.tap do |s|
|
240
|
+
number.times { s << "\s\s" }
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def load_platform(platform, app = nil)
|
245
|
+
if platform.to_sym == :heroku
|
246
|
+
say("Using platform: #{platform}" << (app ? " (app: #{app})" : ""))
|
247
|
+
unless set_env_from_heroku(app)
|
248
|
+
say("Unable to load ENV from Heroku. Do you need to specify an app name?", :red)
|
249
|
+
return false
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
true
|
254
|
+
end
|
255
|
+
|
256
|
+
def read_heroku_env(app = nil)
|
257
|
+
cmd = ['heroku config']
|
258
|
+
cmd << "--app #{app}" if app
|
259
|
+
output = Bundler.with_clean_env { `#{cmd.join("\s")}` }
|
260
|
+
return false unless $?.to_i == 0
|
261
|
+
Hash[output.scan(/(HONEYBADGER_[^:]+):\s*(\S.*)\s*$/)]
|
262
|
+
end
|
263
|
+
|
264
|
+
def set_env_from_heroku(app = nil)
|
265
|
+
return false unless env = read_heroku_env(app)
|
266
|
+
env.each_pair do |k,v|
|
267
|
+
ENV[k] ||= v
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def write_heroku_env(env, app = nil)
|
272
|
+
cmd = ["heroku config:set"]
|
273
|
+
Hash(env).each_pair {|k,v| cmd << "#{k}=#{v}" }
|
274
|
+
cmd << "--app #{app}" if app
|
275
|
+
Bundler.with_clean_env { `#{cmd.join("\s")}` }
|
276
|
+
$?.to_i == 0
|
277
|
+
end
|
278
|
+
|
279
|
+
def test_exception_class
|
280
|
+
exception_name = ENV['EXCEPTION'] || 'HoneybadgerTestingException'
|
281
|
+
Object.const_get(exception_name)
|
282
|
+
rescue
|
283
|
+
Object.const_set(exception_name, Class.new(Exception))
|
284
|
+
end
|
285
|
+
|
286
|
+
def send_test(verbose = true)
|
287
|
+
if defined?(::Rails)
|
288
|
+
rails_test(verbose)
|
289
|
+
else
|
290
|
+
standalone_test
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
def standalone_test
|
295
|
+
Honeybadger.notify(test_exception_class.new('Testing honeybadger via "honeybadger test". If you can see this, it works.'))
|
296
|
+
end
|
297
|
+
|
298
|
+
def rails_test(verbose = true)
|
299
|
+
if verbose
|
300
|
+
::Rails.logger = if defined?(::ActiveSupport::TaggedLogging)
|
301
|
+
::ActiveSupport::TaggedLogging.new(Logger.new(STDOUT))
|
302
|
+
else
|
303
|
+
Logger.new(STDOUT)
|
304
|
+
end
|
305
|
+
::Rails.logger.level = Logger::INFO
|
306
|
+
end
|
307
|
+
|
308
|
+
# Suppress error logging in Rails' exception handling middleware. Rails 3.0
|
309
|
+
# uses ActionDispatch::ShowExceptions to rescue/show exceptions, but does
|
310
|
+
# not log anything but application trace. Rails 3.2 now falls back to
|
311
|
+
# logging the framework trace (moved to ActionDispatch::DebugExceptions),
|
312
|
+
# which caused cluttered output while running the test task.
|
313
|
+
defined?(::ActionDispatch::DebugExceptions) and
|
314
|
+
::ActionDispatch::DebugExceptions.class_eval { def logger(*args) ; @logger ||= Logger.new('/dev/null') ; end }
|
315
|
+
defined?(::ActionDispatch::ShowExceptions) and
|
316
|
+
::ActionDispatch::ShowExceptions.class_eval { def logger(*args) ; @logger ||= Logger.new('/dev/null') ; end }
|
317
|
+
|
318
|
+
# Detect and disable the better_errors gem
|
319
|
+
if defined?(::BetterErrors::Middleware)
|
320
|
+
say('Better Errors detected: temporarily disabling middleware.', :yellow)
|
321
|
+
::BetterErrors::Middleware.class_eval { def call(env) @app.call(env); end }
|
322
|
+
end
|
323
|
+
|
324
|
+
begin
|
325
|
+
require './app/controllers/application_controller'
|
326
|
+
rescue LoadError
|
327
|
+
nil
|
328
|
+
end
|
329
|
+
|
330
|
+
unless defined?(::ApplicationController)
|
331
|
+
say('Error: No ApplicationController found.', :red)
|
332
|
+
return false
|
333
|
+
end
|
334
|
+
|
335
|
+
say('Setting up the Controller.')
|
336
|
+
::ApplicationController.class_eval do
|
337
|
+
# This is to bypass any filters that may prevent access to the action.
|
338
|
+
prepend_before_filter :test_honeybadger
|
339
|
+
def test_honeybadger
|
340
|
+
puts "Raising '#{exception_class.name}' to simulate application failure."
|
341
|
+
raise exception_class.new, 'Testing honeybadger via "rake honeybadger:test". If you can see this, it works.'
|
342
|
+
end
|
343
|
+
|
344
|
+
# Ensure we actually have an action to go to.
|
345
|
+
def verify; end
|
346
|
+
|
347
|
+
def exception_class
|
348
|
+
exception_name = ENV['EXCEPTION'] || 'HoneybadgerTestingException'
|
349
|
+
Object.const_get(exception_name)
|
350
|
+
rescue
|
351
|
+
Object.const_set(exception_name, Class.new(Exception))
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
::Rails.application.routes.draw do
|
356
|
+
match 'verify' => 'application#verify', :as => 'verify', :via => :get
|
357
|
+
end
|
358
|
+
|
359
|
+
say('Processing request.')
|
360
|
+
|
361
|
+
ssl = defined?(::Rails.configuration.force_ssl) && ::Rails.configuration.force_ssl
|
362
|
+
env = ::Rack::MockRequest.env_for("http#{ ssl ? 's' : nil }://www.example.com/verify", 'REMOTE_ADDR' => '127.0.0.1')
|
363
|
+
|
364
|
+
::Rails.application.call(env)
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|
@@ -0,0 +1,333 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'delegate'
|
3
|
+
require 'logger'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'openssl'
|
6
|
+
|
7
|
+
require 'honeybadger/version'
|
8
|
+
require 'honeybadger/logging'
|
9
|
+
require 'honeybadger/backend'
|
10
|
+
require 'honeybadger/config/defaults'
|
11
|
+
require 'honeybadger/util/http'
|
12
|
+
require 'honeybadger/logging'
|
13
|
+
|
14
|
+
module Honeybadger
|
15
|
+
class Config
|
16
|
+
extend Forwardable
|
17
|
+
|
18
|
+
include Logging::Helper
|
19
|
+
|
20
|
+
class ConfigError < StandardError; end
|
21
|
+
|
22
|
+
autoload :Callbacks, 'honeybadger/config/callbacks'
|
23
|
+
autoload :Env, 'honeybadger/config/env'
|
24
|
+
autoload :Yaml, 'honeybadger/config/yaml'
|
25
|
+
|
26
|
+
KEY_REPLACEMENT = Regexp.new('[^a-z\d_]', Regexp::IGNORECASE).freeze
|
27
|
+
|
28
|
+
DISALLOWED_KEYS = [:'config.path'].freeze
|
29
|
+
|
30
|
+
DOTTED_KEY = Regexp.new('\A([^\.]+)\.(.+)\z').freeze
|
31
|
+
|
32
|
+
NOT_BLANK = Regexp.new('\S').freeze
|
33
|
+
|
34
|
+
FEATURES = [:notices, :local_variables, :metrics, :traces].freeze
|
35
|
+
|
36
|
+
def initialize(opts = {})
|
37
|
+
l = opts.delete(:logger)
|
38
|
+
|
39
|
+
@values = opts
|
40
|
+
|
41
|
+
load_config_from_disk do |yml|
|
42
|
+
update(yml)
|
43
|
+
end
|
44
|
+
|
45
|
+
update(Env.new(ENV))
|
46
|
+
|
47
|
+
self.logger = Logging::SupplementedLogger.new(build_logger(l))
|
48
|
+
Logging::BootLogger.instance.flush(logger)
|
49
|
+
|
50
|
+
@features = Hash[FEATURES.map{|f| [f, true] }]
|
51
|
+
end
|
52
|
+
|
53
|
+
def_delegators :@values, :update
|
54
|
+
|
55
|
+
attr_reader :features
|
56
|
+
|
57
|
+
def get(key)
|
58
|
+
key = key.to_sym
|
59
|
+
if @values.include?(key)
|
60
|
+
@values[key]
|
61
|
+
else
|
62
|
+
DEFAULTS[key]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
alias [] :get
|
66
|
+
|
67
|
+
def set(key, value)
|
68
|
+
@values[key] = value
|
69
|
+
end
|
70
|
+
alias []= :set
|
71
|
+
|
72
|
+
def to_hash(defaults = false)
|
73
|
+
hash = defaults ? DEFAULTS.merge(@values) : @values
|
74
|
+
undotify_keys(hash.select {|k,v| DEFAULTS.has_key?(k) })
|
75
|
+
end
|
76
|
+
alias :to_h :to_hash
|
77
|
+
|
78
|
+
def backend
|
79
|
+
Backend.for((self[:backend] || default_backend).to_sym).new(self)
|
80
|
+
end
|
81
|
+
|
82
|
+
def public?
|
83
|
+
return true if self[:public]
|
84
|
+
return false if self[:public] == false
|
85
|
+
!self[:env] || self[:environments].include?(self[:env])
|
86
|
+
end
|
87
|
+
|
88
|
+
def default_backend
|
89
|
+
if public?
|
90
|
+
:server
|
91
|
+
else
|
92
|
+
:null
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def valid?
|
97
|
+
self[:api_key] =~ /\S/
|
98
|
+
end
|
99
|
+
|
100
|
+
def logger
|
101
|
+
@logger || Logging::SupplementedLogger.new(Logging::BootLogger.instance)
|
102
|
+
end
|
103
|
+
|
104
|
+
def logger=(logger)
|
105
|
+
@logger = logger.nil? ? Logger.new('/dev/null') : logger
|
106
|
+
end
|
107
|
+
|
108
|
+
# Internal: Optional path to honeybadger.log log file. If nil, STDOUT will be used
|
109
|
+
# instead.
|
110
|
+
#
|
111
|
+
# Returns the Pathname log path if a log path was specified.
|
112
|
+
def log_path
|
113
|
+
if self[:'logging.path'] && self[:'logging.path'] != 'STDOUT'
|
114
|
+
locate_absolute_path(self[:'logging.path'], self[:root])
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Internal: Path to honeybadger.yml configuration file; this should be the root
|
119
|
+
# directory if no path was specified.
|
120
|
+
#
|
121
|
+
# Returns the Pathname configuration path.
|
122
|
+
def config_path
|
123
|
+
locate_absolute_path(Array(self[:'config.path']).first, self[:root])
|
124
|
+
end
|
125
|
+
|
126
|
+
def config_paths
|
127
|
+
Array(self[:'config.path']).map do |c|
|
128
|
+
locate_absolute_path(c, self[:root])
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def ca_bundle_path
|
133
|
+
if self[:'connection.system_ssl_cert_chain'] && File.exist?(OpenSSL::X509::DEFAULT_CERT_FILE)
|
134
|
+
OpenSSL::X509::DEFAULT_CERT_FILE
|
135
|
+
else
|
136
|
+
local_cert_path
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def local_cert_path
|
141
|
+
File.expand_path(File.join('..', '..', '..', 'resources', 'ca-bundle.crt'), __FILE__)
|
142
|
+
end
|
143
|
+
|
144
|
+
def connection_port
|
145
|
+
if self[:'connection.port']
|
146
|
+
self[:'connection.port']
|
147
|
+
elsif self[:'connection.secure']
|
148
|
+
443
|
149
|
+
else
|
150
|
+
80
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def connection_protocol
|
155
|
+
if self[:'connection.secure']
|
156
|
+
'https'
|
157
|
+
else
|
158
|
+
'http'
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def request
|
163
|
+
Thread.current[:__honeybadger_request]
|
164
|
+
end
|
165
|
+
|
166
|
+
def with_request(request, &block)
|
167
|
+
Thread.current[:__honeybadger_request] = request
|
168
|
+
yield
|
169
|
+
ensure
|
170
|
+
Thread.current[:__honeybadger_request] = nil
|
171
|
+
end
|
172
|
+
|
173
|
+
def params_filters
|
174
|
+
self[:'request.filter_keys'] + rails_params_filters
|
175
|
+
end
|
176
|
+
|
177
|
+
def rails_params_filters
|
178
|
+
request && request.env['action_dispatch.parameter_filter'] or []
|
179
|
+
end
|
180
|
+
|
181
|
+
def write
|
182
|
+
path = config_path
|
183
|
+
|
184
|
+
if path.exist?
|
185
|
+
raise ConfigError, "The configuration file #{path} already exists."
|
186
|
+
elsif !path.dirname.writable?
|
187
|
+
raise ConfigError, "The configuration path #{path.dirname} is not writable."
|
188
|
+
end
|
189
|
+
|
190
|
+
File.open(path, 'w+') do |file|
|
191
|
+
file.write(<<-CONFIG)
|
192
|
+
---
|
193
|
+
api_key: #{self[:api_key]}
|
194
|
+
CONFIG
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def log_level
|
199
|
+
case self[:'logging.level'].to_s
|
200
|
+
when /\A(0|debug)\z/i then Logger::DEBUG
|
201
|
+
when /\A(1|info)\z/i then Logger::INFO
|
202
|
+
when /\A(2|warn)\z/i then Logger::WARN
|
203
|
+
when /\A(3|error)\z/i then Logger::ERROR
|
204
|
+
else
|
205
|
+
Logger::INFO
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
def load_plugin?(name)
|
210
|
+
return false if Array(self[:'plugins.skip']).include?(name)
|
211
|
+
return true if self[:plugins].nil?
|
212
|
+
Array(self[:plugins]).include?(name)
|
213
|
+
end
|
214
|
+
|
215
|
+
def ping
|
216
|
+
if result = send_ping
|
217
|
+
@features = symbolize_keys(result['features']) if result['features']
|
218
|
+
return true
|
219
|
+
end
|
220
|
+
|
221
|
+
false
|
222
|
+
end
|
223
|
+
|
224
|
+
def framework
|
225
|
+
if self[:framework] =~ NOT_BLANK
|
226
|
+
self[:framework].to_sym
|
227
|
+
elsif defined?(::Rails::VERSION) && ::Rails::VERSION::STRING > '3.0'
|
228
|
+
:rails
|
229
|
+
elsif defined?(::Sinatra::VERSION)
|
230
|
+
:sinatra
|
231
|
+
elsif defined?(::Rack.release)
|
232
|
+
:rack
|
233
|
+
else
|
234
|
+
:ruby
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def framework_name
|
239
|
+
case framework
|
240
|
+
when :rails then "Rails #{::Rails::VERSION::STRING}"
|
241
|
+
when :sinatra then "Sinatra #{::Sinatra::VERSION}"
|
242
|
+
when :rack then "Rack #{::Rack.release}"
|
243
|
+
else
|
244
|
+
"Ruby #{RUBY_VERSION}"
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
private
|
249
|
+
|
250
|
+
def ping_payload
|
251
|
+
{
|
252
|
+
version: VERSION,
|
253
|
+
framework: framework_name,
|
254
|
+
environment: self[:env],
|
255
|
+
hostname: self[:hostname],
|
256
|
+
config: to_hash
|
257
|
+
}
|
258
|
+
end
|
259
|
+
|
260
|
+
def send_ping
|
261
|
+
payload = ping_payload.to_json
|
262
|
+
debug { sprintf('ping payload=%s', payload.dump) }
|
263
|
+
response = backend.notify(:ping, payload)
|
264
|
+
if response.success?
|
265
|
+
debug { sprintf('ping response=%s', response.body.dump) }
|
266
|
+
JSON.parse(response.body)
|
267
|
+
else
|
268
|
+
warn do
|
269
|
+
msg = sprintf('ping failure code=%s', response.code)
|
270
|
+
msg << sprintf(' message=%s', response.message.dump) if response.message =~ NOT_BLANK
|
271
|
+
msg
|
272
|
+
end
|
273
|
+
nil
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
def locate_absolute_path(path, root)
|
278
|
+
path = Pathname.new(path)
|
279
|
+
if path.absolute?
|
280
|
+
path
|
281
|
+
else
|
282
|
+
Pathname.new(root).join(path)
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
def build_logger(default = nil)
|
287
|
+
if path = log_path
|
288
|
+
FileUtils.mkdir_p(path.dirname) unless path.dirname.writable?
|
289
|
+
Logger.new(path).tap do |logger|
|
290
|
+
logger.level = log_level
|
291
|
+
logger.formatter = Logger::Formatter.new
|
292
|
+
end
|
293
|
+
elsif self[:'logging.path'] != 'STDOUT' && default
|
294
|
+
default
|
295
|
+
else
|
296
|
+
logger = Logger.new($stdout)
|
297
|
+
logger.level = log_level
|
298
|
+
logger.formatter = lambda do |severity, datetime, progname, msg|
|
299
|
+
"#{msg}\n"
|
300
|
+
end
|
301
|
+
Logging::FormattedLogger.new(logger)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def load_config_from_disk
|
306
|
+
if (path = config_paths.find(&:exist?)) && path.file?
|
307
|
+
Yaml.new(path, self[:env]).tap do |yml|
|
308
|
+
yield(yml) if block_given?
|
309
|
+
end
|
310
|
+
end
|
311
|
+
rescue ConfigError => e
|
312
|
+
logger.error("Error while loading config from disk: #{e}")
|
313
|
+
nil
|
314
|
+
end
|
315
|
+
|
316
|
+
def undotify_keys(hash)
|
317
|
+
{}.tap do |new_hash|
|
318
|
+
hash.each_pair do |k,v|
|
319
|
+
if k.to_s =~ DOTTED_KEY
|
320
|
+
new_hash[$1] ||= {}
|
321
|
+
new_hash[$1] = undotify_keys(new_hash[$1].merge({$2 => v}))
|
322
|
+
else
|
323
|
+
new_hash[k] = v
|
324
|
+
end
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
def symbolize_keys(hash)
|
330
|
+
Hash[hash.map {|k,v| [k.to_sym, v] }]
|
331
|
+
end
|
332
|
+
end
|
333
|
+
end
|