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.
Files changed (200) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +19 -0
  3. data/README.md +37 -16
  4. data/bin/honeybadger +5 -0
  5. data/lib/honeybadger.rb +167 -191
  6. data/lib/honeybadger/agent.rb +136 -0
  7. data/lib/honeybadger/backend.rb +26 -0
  8. data/lib/honeybadger/backend/base.rb +66 -0
  9. data/lib/honeybadger/backend/debug.rb +12 -0
  10. data/lib/honeybadger/backend/null.rb +16 -0
  11. data/lib/honeybadger/backend/server.rb +51 -0
  12. data/lib/honeybadger/backend/test.rb +24 -0
  13. data/lib/honeybadger/backtrace.rb +29 -24
  14. data/lib/honeybadger/cli.rb +367 -0
  15. data/lib/honeybadger/config.rb +333 -0
  16. data/lib/honeybadger/config/callbacks.rb +70 -0
  17. data/lib/honeybadger/config/defaults.rb +175 -0
  18. data/lib/honeybadger/config/env.rb +40 -0
  19. data/lib/honeybadger/config/yaml.rb +43 -0
  20. data/lib/honeybadger/const.rb +28 -0
  21. data/lib/honeybadger/init/rails.rb +84 -0
  22. data/lib/honeybadger/init/sinatra.rb +27 -0
  23. data/lib/honeybadger/logging.rb +133 -0
  24. data/lib/honeybadger/notice.rb +243 -280
  25. data/lib/honeybadger/plugin.rb +110 -0
  26. data/lib/honeybadger/plugins/delayed_job.rb +22 -0
  27. data/lib/honeybadger/{integrations → plugins}/delayed_job/plugin.rb +6 -7
  28. data/lib/honeybadger/{integrations → plugins}/local_variables.rb +7 -8
  29. data/lib/honeybadger/{integrations → plugins}/net_http.rb +10 -8
  30. data/lib/honeybadger/plugins/passenger.rb +24 -0
  31. data/lib/honeybadger/plugins/rails.rb +61 -0
  32. data/lib/honeybadger/plugins/sidekiq.rb +35 -0
  33. data/lib/honeybadger/{integrations → plugins}/thor.rb +9 -8
  34. data/lib/honeybadger/{integrations → plugins}/unicorn.rb +10 -9
  35. data/lib/honeybadger/rack/error_notifier.rb +44 -27
  36. data/lib/honeybadger/rack/metrics_reporter.rb +41 -0
  37. data/lib/honeybadger/rack/request_hash.rb +50 -0
  38. data/lib/honeybadger/rack/user_feedback.rb +15 -10
  39. data/lib/honeybadger/rack/user_informer.rb +14 -3
  40. data/lib/honeybadger/trace.rb +185 -0
  41. data/lib/honeybadger/util/http.rb +79 -0
  42. data/lib/honeybadger/util/request_sanitizer.rb +35 -0
  43. data/lib/honeybadger/util/sanitizer.rb +71 -0
  44. data/lib/honeybadger/util/stats.rb +31 -0
  45. data/lib/honeybadger/version.rb +4 -0
  46. data/lib/honeybadger/worker.rb +224 -0
  47. data/lib/honeybadger/worker/batch.rb +50 -0
  48. data/lib/honeybadger/worker/metered_queue.rb +80 -0
  49. data/lib/honeybadger/worker/metrics_collection.rb +61 -0
  50. data/lib/honeybadger/worker/metrics_collector.rb +96 -0
  51. data/{lib/honeybadger/capistrano.rb → vendor/capistrano-honeybadger/lib/capistrano/honeybadger.rb} +1 -3
  52. data/vendor/capistrano-honeybadger/lib/capistrano/tasks/deploy.cap +76 -0
  53. data/vendor/capistrano-honeybadger/lib/honeybadger/capistrano.rb +2 -0
  54. data/{lib → vendor/capistrano-honeybadger/lib}/honeybadger/capistrano/legacy.rb +16 -15
  55. data/vendor/thor/lib/thor.rb +484 -0
  56. data/vendor/thor/lib/thor/actions.rb +319 -0
  57. data/vendor/thor/lib/thor/actions/create_file.rb +103 -0
  58. data/vendor/thor/lib/thor/actions/create_link.rb +59 -0
  59. data/vendor/thor/lib/thor/actions/directory.rb +118 -0
  60. data/vendor/thor/lib/thor/actions/empty_directory.rb +135 -0
  61. data/vendor/thor/lib/thor/actions/file_manipulation.rb +316 -0
  62. data/vendor/thor/lib/thor/actions/inject_into_file.rb +107 -0
  63. data/vendor/thor/lib/thor/base.rb +656 -0
  64. data/vendor/thor/lib/thor/command.rb +133 -0
  65. data/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb +77 -0
  66. data/vendor/thor/lib/thor/core_ext/io_binary_read.rb +10 -0
  67. data/vendor/thor/lib/thor/core_ext/ordered_hash.rb +98 -0
  68. data/vendor/thor/lib/thor/error.rb +32 -0
  69. data/vendor/thor/lib/thor/group.rb +281 -0
  70. data/vendor/thor/lib/thor/invocation.rb +178 -0
  71. data/vendor/thor/lib/thor/line_editor.rb +17 -0
  72. data/vendor/thor/lib/thor/line_editor/basic.rb +35 -0
  73. data/vendor/thor/lib/thor/line_editor/readline.rb +88 -0
  74. data/vendor/thor/lib/thor/parser.rb +4 -0
  75. data/vendor/thor/lib/thor/parser/argument.rb +73 -0
  76. data/vendor/thor/lib/thor/parser/arguments.rb +175 -0
  77. data/vendor/thor/lib/thor/parser/option.rb +125 -0
  78. data/vendor/thor/lib/thor/parser/options.rb +218 -0
  79. data/vendor/thor/lib/thor/rake_compat.rb +71 -0
  80. data/vendor/thor/lib/thor/runner.rb +322 -0
  81. data/vendor/thor/lib/thor/shell.rb +81 -0
  82. data/vendor/thor/lib/thor/shell/basic.rb +421 -0
  83. data/vendor/thor/lib/thor/shell/color.rb +149 -0
  84. data/vendor/thor/lib/thor/shell/html.rb +126 -0
  85. data/vendor/thor/lib/thor/util.rb +267 -0
  86. data/vendor/thor/lib/thor/version.rb +3 -0
  87. metadata +97 -305
  88. data/Appraisals +0 -95
  89. data/CHANGELOG.md +0 -422
  90. data/Gemfile +0 -8
  91. data/Gemfile.lock +0 -136
  92. data/Guardfile +0 -5
  93. data/MIT-LICENSE +0 -32
  94. data/Rakefile +0 -159
  95. data/features/metal.feature +0 -20
  96. data/features/rack.feature +0 -55
  97. data/features/rails.feature +0 -343
  98. data/features/rails3.x.feature +0 -26
  99. data/features/rake.feature +0 -25
  100. data/features/sinatra.feature +0 -27
  101. data/features/standalone.feature +0 -73
  102. data/features/step_definitions/metal_steps.rb +0 -24
  103. data/features/step_definitions/rack_steps.rb +0 -18
  104. data/features/step_definitions/rails_steps.rb +0 -270
  105. data/features/step_definitions/rake_steps.rb +0 -17
  106. data/features/step_definitions/standalone_steps.rb +0 -12
  107. data/features/step_definitions/thor_steps.rb +0 -4
  108. data/features/support/env.rb +0 -22
  109. data/features/support/honeybadger_failure_shim.rb.template +0 -5
  110. data/features/support/honeybadger_shim.rb.template +0 -6
  111. data/features/support/rails.rb +0 -202
  112. data/features/support/rake/Rakefile +0 -68
  113. data/features/support/test.thor +0 -22
  114. data/features/thor.feature +0 -5
  115. data/gemfiles/binding_of_caller.gemfile +0 -13
  116. data/gemfiles/delayed_job.gemfile +0 -13
  117. data/gemfiles/rack.gemfile +0 -13
  118. data/gemfiles/rails.gemfile +0 -16
  119. data/gemfiles/rails2.3.gemfile +0 -15
  120. data/gemfiles/rails3.0.gemfile +0 -16
  121. data/gemfiles/rails3.1.gemfile +0 -16
  122. data/gemfiles/rails3.2.gemfile +0 -16
  123. data/gemfiles/rails4.0.gemfile +0 -16
  124. data/gemfiles/rails4.1.gemfile +0 -16
  125. data/gemfiles/rake.gemfile +0 -13
  126. data/gemfiles/sinatra.gemfile +0 -13
  127. data/gemfiles/standalone.gemfile +0 -12
  128. data/gemfiles/thor.gemfile +0 -13
  129. data/generators/honeybadger/honeybadger_generator.rb +0 -95
  130. data/generators/honeybadger/lib/insert_commands.rb +0 -34
  131. data/generators/honeybadger/lib/rake_commands.rb +0 -24
  132. data/generators/honeybadger/templates/capistrano_hook.rb +0 -6
  133. data/generators/honeybadger/templates/honeybadger_tasks.rake +0 -25
  134. data/generators/honeybadger/templates/initializer.rb +0 -6
  135. data/honeybadger.gemspec +0 -174
  136. data/lib/honeybadger/array.rb +0 -53
  137. data/lib/honeybadger/capistrano/tasks.rake +0 -73
  138. data/lib/honeybadger/configuration.rb +0 -397
  139. data/lib/honeybadger/dependency.rb +0 -65
  140. data/lib/honeybadger/integrations.rb +0 -9
  141. data/lib/honeybadger/integrations/delayed_job.rb +0 -20
  142. data/lib/honeybadger/integrations/passenger.rb +0 -18
  143. data/lib/honeybadger/integrations/sidekiq.rb +0 -37
  144. data/lib/honeybadger/monitor.rb +0 -17
  145. data/lib/honeybadger/monitor/railtie.rb +0 -53
  146. data/lib/honeybadger/monitor/sender.rb +0 -44
  147. data/lib/honeybadger/monitor/trace.rb +0 -187
  148. data/lib/honeybadger/monitor/worker.rb +0 -169
  149. data/lib/honeybadger/payload.rb +0 -101
  150. data/lib/honeybadger/rack.rb +0 -12
  151. data/lib/honeybadger/rails.rb +0 -45
  152. data/lib/honeybadger/rails/action_controller_catcher.rb +0 -30
  153. data/lib/honeybadger/rails/controller_methods.rb +0 -78
  154. data/lib/honeybadger/rails/middleware/exceptions_catcher.rb +0 -29
  155. data/lib/honeybadger/rails3_tasks.rb +0 -94
  156. data/lib/honeybadger/railtie.rb +0 -52
  157. data/lib/honeybadger/rake_handler.rb +0 -66
  158. data/lib/honeybadger/sender.rb +0 -185
  159. data/lib/honeybadger/shared_tasks.rb +0 -56
  160. data/lib/honeybadger/stats.rb +0 -29
  161. data/lib/honeybadger/tasks.rb +0 -95
  162. data/lib/honeybadger/user_feedback.rb +0 -8
  163. data/lib/honeybadger/user_informer.rb +0 -8
  164. data/lib/honeybadger_tasks.rb +0 -69
  165. data/lib/rails/generators/honeybadger/honeybadger_generator.rb +0 -99
  166. data/rails/init.rb +0 -1
  167. data/resources/README.md +0 -34
  168. data/script/integration_test.rb +0 -38
  169. data/spec/allocation_stats.rb +0 -32
  170. data/spec/honeybadger/backtrace_spec.rb +0 -242
  171. data/spec/honeybadger/capistrano_spec.rb +0 -36
  172. data/spec/honeybadger/configuration_spec.rb +0 -328
  173. data/spec/honeybadger/dependency_spec.rb +0 -134
  174. data/spec/honeybadger/integrations/delayed_job_spec.rb +0 -82
  175. data/spec/honeybadger/integrations/local_variables_spec.rb +0 -60
  176. data/spec/honeybadger/integrations/net_http_spec.rb +0 -29
  177. data/spec/honeybadger/integrations/passenger_spec.rb +0 -29
  178. data/spec/honeybadger/integrations/sidekiq_spec.rb +0 -60
  179. data/spec/honeybadger/integrations/thor_spec.rb +0 -32
  180. data/spec/honeybadger/integrations/unicorn_spec.rb +0 -40
  181. data/spec/honeybadger/logger_spec.rb +0 -79
  182. data/spec/honeybadger/monitor/trace_spec.rb +0 -65
  183. data/spec/honeybadger/monitor/worker_spec.rb +0 -274
  184. data/spec/honeybadger/notice_spec.rb +0 -669
  185. data/spec/honeybadger/notifier_spec.rb +0 -328
  186. data/spec/honeybadger/payload_spec.rb +0 -162
  187. data/spec/honeybadger/rack_spec.rb +0 -85
  188. data/spec/honeybadger/rails/action_controller_spec.rb +0 -328
  189. data/spec/honeybadger/rails_spec.rb +0 -37
  190. data/spec/honeybadger/sender_spec.rb +0 -317
  191. data/spec/honeybadger/stats_spec.rb +0 -57
  192. data/spec/honeybadger/user_feedback_spec.rb +0 -80
  193. data/spec/honeybadger/user_informer_spec.rb +0 -30
  194. data/spec/honeybadger_tasks_spec.rb +0 -171
  195. data/spec/spec_helper.rb +0 -24
  196. data/spec/support/array_including.rb +0 -31
  197. data/spec/support/backtraced_exception.rb +0 -9
  198. data/spec/support/collected_sender.rb +0 -12
  199. data/spec/support/defines_constants.rb +0 -18
  200. 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