honeybadger 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. data/Gemfile +13 -0
  2. data/Gemfile.lock +114 -0
  3. data/Guardfile +5 -0
  4. data/MIT-LICENSE +22 -0
  5. data/README.md +271 -0
  6. data/Rakefile +261 -0
  7. data/SUPPORTED_RAILS_VERSIONS +26 -0
  8. data/TESTING.md +33 -0
  9. data/features/metal.feature +18 -0
  10. data/features/rack.feature +56 -0
  11. data/features/rails.feature +211 -0
  12. data/features/rake.feature +27 -0
  13. data/features/sinatra.feature +29 -0
  14. data/features/step_definitions/file_steps.rb +10 -0
  15. data/features/step_definitions/metal_steps.rb +23 -0
  16. data/features/step_definitions/rack_steps.rb +23 -0
  17. data/features/step_definitions/rails_application_steps.rb +394 -0
  18. data/features/step_definitions/rake_steps.rb +17 -0
  19. data/features/support/env.rb +17 -0
  20. data/features/support/honeybadger_shim.rb.template +8 -0
  21. data/features/support/rails.rb +201 -0
  22. data/features/support/rake/Rakefile +68 -0
  23. data/features/support/terminal.rb +107 -0
  24. data/generators/honeybadger/honeybadger_generator.rb +94 -0
  25. data/generators/honeybadger/lib/insert_commands.rb +34 -0
  26. data/generators/honeybadger/lib/rake_commands.rb +24 -0
  27. data/generators/honeybadger/templates/capistrano_hook.rb +6 -0
  28. data/generators/honeybadger/templates/honeybadger_tasks.rake +25 -0
  29. data/generators/honeybadger/templates/initializer.rb +6 -0
  30. data/honeybadger.gemspec +109 -0
  31. data/lib/honeybadger.rb +162 -0
  32. data/lib/honeybadger/backtrace.rb +123 -0
  33. data/lib/honeybadger/capistrano.rb +43 -0
  34. data/lib/honeybadger/configuration.rb +273 -0
  35. data/lib/honeybadger/notice.rb +314 -0
  36. data/lib/honeybadger/rack.rb +55 -0
  37. data/lib/honeybadger/rails.rb +34 -0
  38. data/lib/honeybadger/rails/action_controller_catcher.rb +30 -0
  39. data/lib/honeybadger/rails/controller_methods.rb +69 -0
  40. data/lib/honeybadger/rails/middleware/exceptions_catcher.rb +29 -0
  41. data/lib/honeybadger/rails3_tasks.rb +84 -0
  42. data/lib/honeybadger/railtie.rb +45 -0
  43. data/lib/honeybadger/rake_handler.rb +65 -0
  44. data/lib/honeybadger/sender.rb +120 -0
  45. data/lib/honeybadger/shared_tasks.rb +36 -0
  46. data/lib/honeybadger/tasks.rb +82 -0
  47. data/lib/honeybadger_tasks.rb +65 -0
  48. data/lib/rails/generators/honeybadger/honeybadger_generator.rb +99 -0
  49. data/rails/init.rb +1 -0
  50. data/resources/README.md +34 -0
  51. data/resources/ca-bundle.crt +3376 -0
  52. data/script/integration_test.rb +38 -0
  53. data/test/test_helper.rb +143 -0
  54. data/test/unit/backtrace_test.rb +180 -0
  55. data/test/unit/capistrano_test.rb +34 -0
  56. data/test/unit/configuration_test.rb +201 -0
  57. data/test/unit/honeybadger_tasks_test.rb +163 -0
  58. data/test/unit/logger_test.rb +72 -0
  59. data/test/unit/notice_test.rb +406 -0
  60. data/test/unit/notifier_test.rb +245 -0
  61. data/test/unit/rack_test.rb +56 -0
  62. data/test/unit/rails/action_controller_catcher_test.rb +300 -0
  63. data/test/unit/rails_test.rb +35 -0
  64. data/test/unit/sender_test.rb +257 -0
  65. metadata +315 -0
@@ -0,0 +1,55 @@
1
+ module Honeybadger
2
+ # Middleware for Rack applications. Any errors raised by the upstream
3
+ # application will be delivered to Honeybadger and re-raised.
4
+ #
5
+ # Synopsis:
6
+ #
7
+ # require 'rack'
8
+ # require 'honeybadger'
9
+ #
10
+ # Honeybadger.configure do |config|
11
+ # config.api_key = 'my_api_key'
12
+ # end
13
+ #
14
+ # app = Rack::Builder.app do
15
+ # run lambda { |env| raise "Rack down" }
16
+ # end
17
+ #
18
+ # use Honeybadger::Rack
19
+ # run app
20
+ #
21
+ # Use a standard Honeybadger.configure call to configure your api key.
22
+ class Rack
23
+ def initialize(app)
24
+ @app = app
25
+ Honeybadger.configuration.logger ||= Logger.new STDOUT
26
+ end
27
+
28
+ def ignored_user_agent?(env)
29
+ true if Honeybadger.
30
+ configuration.
31
+ ignore_user_agent.
32
+ flatten.
33
+ any? { |ua| ua === env['HTTP_USER_AGENT'] }
34
+ end
35
+
36
+ def notify_honeybadger(exception,env)
37
+ Honeybadger.notify_or_ignore(exception,:rack_env => env) unless ignored_user_agent?(env)
38
+ end
39
+
40
+ def call(env)
41
+ begin
42
+ response = @app.call(env)
43
+ rescue Exception => raised
44
+ env['honeybadger.error_id'] = notify_honeybadger(raised,env)
45
+ raise
46
+ end
47
+
48
+ if env['rack.exception']
49
+ env['honeybadger.error_id'] = notify_honeybadger(env['rack.exception'],env)
50
+ end
51
+
52
+ response
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,34 @@
1
+ require 'honeybadger'
2
+ require 'honeybadger/rails/controller_methods'
3
+ require 'honeybadger/rails/action_controller_catcher'
4
+
5
+ module Honeybadger
6
+ module Rails
7
+ def self.initialize
8
+ if defined?(ActionController::Base)
9
+ ActionController::Base.send(:include, Honeybadger::Rails::ActionControllerCatcher)
10
+ ActionController::Base.send(:include, Honeybadger::Rails::ControllerMethods)
11
+ end
12
+
13
+ rails_logger = if defined?(::Rails.logger)
14
+ ::Rails.logger
15
+ elsif defined?(RAILS_DEFAULT_LOGGER)
16
+ RAILS_DEFAULT_LOGGER
17
+ end
18
+
19
+ if defined?(::Rails.configuration) && ::Rails.configuration.respond_to?(:middleware)
20
+ ::Rails.configuration.middleware.insert_after 'ActionController::Failsafe',
21
+ Honeybadger::Rack
22
+ end
23
+
24
+ Honeybadger.configure(true) do |config|
25
+ config.logger = rails_logger
26
+ config.environment_name = defined?(::Rails.env) && ::Rails.env || defined?(RAILS_ENV) && RAILS_ENV
27
+ config.project_root = defined?(::Rails.root) && ::Rails.root || defined?(RAILS_ROOT) && RAILS_ROOT
28
+ config.framework = defined?(::Rails.version) && "Rails: #{::Rails.version}" || defined?(::Rails::VERSION::STRING) && "Rails: #{::Rails::VERSION::STRING}"
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ Honeybadger::Rails.initialize
@@ -0,0 +1,30 @@
1
+ module Honeybadger
2
+ module Rails
3
+ module ActionControllerCatcher
4
+
5
+ # Sets up an alias chain to catch exceptions when Rails does
6
+ def self.included(base) #:nodoc:
7
+ base.send(:alias_method, :rescue_action_in_public_without_honeybadger, :rescue_action_in_public)
8
+ base.send(:alias_method, :rescue_action_in_public, :rescue_action_in_public_with_honeybadger)
9
+ end
10
+
11
+ private
12
+
13
+ # Overrides the rescue_action method in ActionController::Base, but does not inhibit
14
+ # any custom processing that is defined with Rails 2's exception helpers.
15
+ def rescue_action_in_public_with_honeybadger(exception)
16
+ unless honeybadger_ignore_user_agent?
17
+ error_id = Honeybadger.notify_or_ignore(exception, honeybadger_request_data)
18
+ request.env['honeybadger.error_id'] = error_id
19
+ end
20
+ rescue_action_in_public_without_honeybadger(exception)
21
+ end
22
+
23
+ def honeybadger_ignore_user_agent? #:nodoc:
24
+ # Rails 1.2.6 doesn't have request.user_agent, so check for it here
25
+ user_agent = request.respond_to?(:user_agent) ? request.user_agent : request.env["HTTP_USER_AGENT"]
26
+ Honeybadger.configuration.ignore_user_agent.flatten.any? { |ua| ua === user_agent }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,69 @@
1
+ module Honeybadger
2
+ module Rails
3
+ module ControllerMethods
4
+ def honeybadger_request_data
5
+ { :parameters => honeybadger_filter_if_filtering(params.to_hash),
6
+ :session_data => honeybadger_filter_if_filtering(honeybadger_session_data),
7
+ :controller => params[:controller],
8
+ :action => params[:action],
9
+ :url => honeybadger_request_url,
10
+ :cgi_data => honeybadger_filter_if_filtering(request.env) }
11
+ end
12
+
13
+ private
14
+
15
+ # This method should be used for sending manual notifications while you are still
16
+ # inside the controller. Otherwise it works like Honeybadger.notify.
17
+ def notify_honeybadger(hash_or_exception)
18
+ unless honeybadger_local_request?
19
+ Honeybadger.notify(hash_or_exception, honeybadger_request_data)
20
+ end
21
+ end
22
+
23
+ def honeybadger_local_request?
24
+ if defined?(::Rails.application.config)
25
+ ::Rails.application.config.consider_all_requests_local || (request.local? && (!request.env["HTTP_X_FORWARDED_FOR"]))
26
+ else
27
+ consider_all_requests_local || (local_request? && (!request.env["HTTP_X_FORWARDED_FOR"]))
28
+ end
29
+ end
30
+
31
+ def honeybadger_ignore_user_agent? #:nodoc:
32
+ # Rails 1.2.6 doesn't have request.user_agent, so check for it here
33
+ user_agent = request.respond_to?(:user_agent) ? request.user_agent : request.env["HTTP_USER_AGENT"]
34
+ Honeybadger.configuration.ignore_user_agent.flatten.any? { |ua| ua === user_agent }
35
+ end
36
+
37
+ def honeybadger_filter_if_filtering(hash)
38
+ return hash if ! hash.is_a?(Hash)
39
+
40
+ if respond_to?(:filter_parameters) # Rails 2
41
+ filter_parameters(hash)
42
+ elsif defined?(ActionDispatch::Http::ParameterFilter) # Rails 3
43
+ ActionDispatch::Http::ParameterFilter.new(::Rails.application.config.filter_parameters).filter(hash)
44
+ else
45
+ hash
46
+ end rescue hash
47
+ end
48
+
49
+ def honeybadger_session_data
50
+ if session.respond_to?(:to_hash)
51
+ session.to_hash
52
+ else
53
+ session.data
54
+ end
55
+ end
56
+
57
+ def honeybadger_request_url
58
+ url = "#{request.protocol}#{request.host}"
59
+
60
+ unless [80, 443].include?(request.port)
61
+ url << ":#{request.port}"
62
+ end
63
+
64
+ url << request.fullpath
65
+ url
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,29 @@
1
+ module Honeybadger
2
+ module Rails
3
+ module Middleware
4
+ module ExceptionsCatcher
5
+ def self.included(base)
6
+ base.send(:alias_method_chain,:render_exception,:honeybadger)
7
+ end
8
+
9
+ def skip_user_agent?(env)
10
+ user_agent = env["HTTP_USER_AGENT"]
11
+ ::Honeybadger.configuration.ignore_user_agent.flatten.any? { |ua| ua === user_agent }
12
+ rescue
13
+ false
14
+ end
15
+
16
+ def render_exception_with_honeybadger(env,exception)
17
+ controller = env['action_controller.instance']
18
+ env['honeybadger.error_id'] = Honeybadger.
19
+ notify_or_ignore(exception,
20
+ (controller.try(:honeybadger_request_data) || {:rack_env => env})) unless skip_user_agent?(env)
21
+ if defined?(controller.rescue_action_in_public_without_honeybadger)
22
+ controller.rescue_action_in_public_without_honeybadger(exception)
23
+ end
24
+ render_exception_without_honeybadger(env,exception)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,84 @@
1
+ require 'honeybadger'
2
+ require File.join(File.dirname(__FILE__), 'shared_tasks')
3
+
4
+ namespace :honeybadger do
5
+ desc "Verify your gem installation by sending a test exception to the honeybadger service"
6
+ task :test => [:environment] do
7
+ Rails.logger = defined?(ActiveSupport::TaggedLogging) ?
8
+ ActiveSupport::TaggedLogging.new(Logger.new(STDOUT)) :
9
+ Logger.new(STDOUT)
10
+
11
+ Rails.logger.level = Logger::DEBUG
12
+ Honeybadger.configure(true) do |config|
13
+ config.logger = Rails.logger
14
+ end
15
+
16
+ require './app/controllers/application_controller'
17
+
18
+ class HoneybadgerTestingException < RuntimeError; end
19
+
20
+ unless Honeybadger.configuration.api_key
21
+ puts "Honeybadger needs an API key configured! Check the README to see how to add it."
22
+ exit
23
+ end
24
+
25
+ Honeybadger.configuration.development_environments = []
26
+
27
+ puts "Configuration:"
28
+ Honeybadger.configuration.to_hash.each do |key, value|
29
+ puts sprintf("%25s: %s", key.to_s, value.inspect.slice(0, 55))
30
+ end
31
+
32
+ unless defined?(ApplicationController)
33
+ puts "No ApplicationController found"
34
+ exit
35
+ end
36
+
37
+ puts 'Setting up the Controller.'
38
+ class ApplicationController
39
+ # This is to bypass any filters that may prevent access to the action.
40
+ prepend_before_filter :test_honeybadger
41
+ def test_honeybadger
42
+ puts "Raising '#{exception_class.name}' to simulate application failure."
43
+ raise exception_class.new, 'Testing honeybadger via "rake honeybadger:test". If you can see this, it works.'
44
+ end
45
+
46
+ # def rescue_action(exception)
47
+ # rescue_action_in_public exception
48
+ # end
49
+
50
+ # Ensure we actually have an action to go to.
51
+ def verify; end
52
+
53
+ # def consider_all_requests_local
54
+ # false
55
+ # end
56
+
57
+ # def local_request?
58
+ # false
59
+ # end
60
+
61
+ def exception_class
62
+ exception_name = ENV['EXCEPTION'] || "HoneybadgerTestingException"
63
+ Object.const_get(exception_name)
64
+ rescue
65
+ Object.const_set(exception_name, Class.new(Exception))
66
+ end
67
+
68
+ def logger
69
+ nil
70
+ end
71
+ end
72
+ class HoneybadgerVerificationController < ApplicationController; end
73
+
74
+ Rails.application.routes_reloader.execute_if_updated
75
+ Rails.application.routes.draw do
76
+ match 'verify' => 'application#verify', :as => 'verify'
77
+ end
78
+
79
+ puts 'Processing request.'
80
+ env = Rack::MockRequest.env_for("/verify")
81
+
82
+ Rails.application.call(env)
83
+ end
84
+ end
@@ -0,0 +1,45 @@
1
+ require 'honeybadger'
2
+ require 'rails'
3
+
4
+ module Honeybadger
5
+ class Railtie < Rails::Railtie
6
+ rake_tasks do
7
+ require 'honeybadger/rake_handler'
8
+ require "honeybadger/rails3_tasks"
9
+ end
10
+
11
+ initializer "honeybadger.use_rack_middleware" do |app|
12
+ app.config.middleware.insert 0, "Honeybadger::Rack"
13
+ end
14
+
15
+ config.after_initialize do
16
+ Honeybadger.configure(true) do |config|
17
+ config.logger ||= ::Rails.logger
18
+ config.environment_name ||= ::Rails.env
19
+ config.project_root ||= ::Rails.root
20
+ config.framework = "Rails: #{::Rails::VERSION::STRING}"
21
+ end
22
+
23
+ ActiveSupport.on_load(:action_controller) do
24
+ # Lazily load action_controller methods
25
+ #
26
+ require 'honeybadger/rails/controller_methods'
27
+
28
+ include Honeybadger::Rails::ControllerMethods
29
+ end
30
+
31
+ if defined?(::ActionDispatch::DebugExceptions)
32
+ # We should catch the exceptions in ActionDispatch::DebugExceptions in Rails 3.2.x.
33
+ #
34
+ require 'honeybadger/rails/middleware/exceptions_catcher'
35
+ ::ActionDispatch::DebugExceptions.send(:include,Honeybadger::Rails::Middleware::ExceptionsCatcher)
36
+ elsif defined?(::ActionDispatch::ShowExceptions)
37
+ # ActionDispatch::DebugExceptions is not defined in Rails 3.0.x and 3.1.x so
38
+ # catch the exceptions in ShowExceptions.
39
+ #
40
+ require 'honeybadger/rails/middleware/exceptions_catcher'
41
+ ::ActionDispatch::ShowExceptions.send(:include,Honeybadger::Rails::Middleware::ExceptionsCatcher)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,65 @@
1
+ # Patch Rake::Application to handle errors with Honeybadger
2
+ module Honeybadger::RakeHandler
3
+ def self.included(klass)
4
+ klass.class_eval do
5
+ include Rake087Methods unless defined?(Rake::VERSION) && Rake::VERSION >= '0.9.0'
6
+ alias_method :display_error_message_without_honeybadger, :display_error_message
7
+ alias_method :display_error_message, :display_error_message_with_honeybadger
8
+ end
9
+ end
10
+
11
+ def display_error_message_with_honeybadger(ex)
12
+ if Honeybadger.sender && Honeybadger.configuration &&
13
+ (Honeybadger.configuration.rescue_rake_exceptions ||
14
+ (Honeybadger.configuration.rescue_rake_exceptions===nil && !self.tty_output?))
15
+
16
+ Honeybadger.notify_or_ignore(ex, :component => reconstruct_command_line, :cgi_data => ENV)
17
+ end
18
+
19
+ display_error_message_without_honeybadger(ex)
20
+ end
21
+
22
+ def reconstruct_command_line
23
+ "rake #{ARGV.join( ' ' )}"
24
+ end
25
+
26
+ # This module brings Rake 0.8.7 error handling to 0.9.0 standards
27
+ module Rake087Methods
28
+ # Method taken from Rake 0.9.0 source
29
+ #
30
+ # Provide standard exception handling for the given block.
31
+ def standard_exception_handling
32
+ begin
33
+ yield
34
+ rescue SystemExit => ex
35
+ # Exit silently with current status
36
+ raise
37
+ rescue OptionParser::InvalidOption => ex
38
+ $stderr.puts ex.message
39
+ exit(false)
40
+ rescue Exception => ex
41
+ # Exit with error message
42
+ display_error_message(ex)
43
+ exit(false)
44
+ end
45
+ end
46
+
47
+ # Method extracted from Rake 0.8.7 source
48
+ def display_error_message(ex)
49
+ $stderr.puts "#{name} aborted!"
50
+ $stderr.puts ex.message
51
+ if options.trace
52
+ $stderr.puts ex.backtrace.join("\n")
53
+ else
54
+ $stderr.puts ex.backtrace.find {|str| str =~ /#{@rakefile}/ } || ""
55
+ $stderr.puts "(See full trace by running task with --trace)"
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ Rake.application.instance_eval do
62
+ class << self
63
+ include Honeybadger::RakeHandler
64
+ end
65
+ end
@@ -0,0 +1,120 @@
1
+ module Honeybadger
2
+ class Sender
3
+ NOTICES_URI = '/v1/notices/'.freeze
4
+ HTTP_ERRORS = [Timeout::Error,
5
+ Errno::EINVAL,
6
+ Errno::ECONNRESET,
7
+ EOFError,
8
+ Net::HTTPBadResponse,
9
+ Net::HTTPHeaderSyntaxError,
10
+ Net::ProtocolError,
11
+ Errno::ECONNREFUSED].freeze
12
+
13
+ def initialize(options = {})
14
+ [ :api_key,
15
+ :proxy_host,
16
+ :proxy_port,
17
+ :proxy_user,
18
+ :proxy_pass,
19
+ :protocol,
20
+ :host,
21
+ :port,
22
+ :secure,
23
+ :use_system_ssl_cert_chain,
24
+ :http_open_timeout,
25
+ :http_read_timeout
26
+ ].each do |option|
27
+ instance_variable_set("@#{option}", options[option])
28
+ end
29
+ end
30
+
31
+ # Public: Sends the notice data off to Honeybadger for processing.
32
+ #
33
+ # data - The JSON notice to be sent off
34
+ #
35
+ # Returns nothing
36
+ def send_to_honeybadger(data)
37
+ http = setup_http_connection
38
+ headers = HEADERS
39
+
40
+ headers.merge!({ 'X-API-Key' => api_key}) if api_key.present?
41
+
42
+ response = begin
43
+ http.post(url.path, data, headers)
44
+ rescue *HTTP_ERRORS => e
45
+ log(:error, "Unable to contact the Honeybadger server. HTTP Error=#{e}")
46
+ nil
47
+ end
48
+
49
+ case response
50
+ when Net::HTTPSuccess then
51
+ log(:info, "Success: #{response.class}", response)
52
+ else
53
+ log(:error, "Failure: #{response.class}", response)
54
+ end
55
+
56
+ if response && response.respond_to?(:body)
57
+ notice = JSON.parse(response.body)
58
+ error_id = notice['id']
59
+ end
60
+ rescue => e
61
+ log(:error, "[Honeybadger::Sender#send_to_honeybadger] Cannot send notification. Error: #{e.class} - #{e.message}\nBacktrace:\n#{e.backtrace.join("\n\t")}")
62
+ nil
63
+ end
64
+
65
+ attr_reader :api_key,
66
+ :proxy_host,
67
+ :proxy_port,
68
+ :proxy_user,
69
+ :proxy_pass,
70
+ :protocol,
71
+ :host,
72
+ :port,
73
+ :secure,
74
+ :use_system_ssl_cert_chain,
75
+ :http_open_timeout,
76
+ :http_read_timeout
77
+
78
+ alias_method :secure?, :secure
79
+ alias_method :use_system_ssl_cert_chain?, :use_system_ssl_cert_chain
80
+
81
+ private
82
+
83
+ def url
84
+ URI.parse("#{protocol}://#{host}:#{port}").merge(NOTICES_URI)
85
+ end
86
+
87
+ def log(level, message, response = nil)
88
+ logger.send(level, LOG_PREFIX + message) if logger
89
+ Honeybadger.report_environment_info
90
+ Honeybadger.report_response_body(response.body) if response && response.respond_to?(:body)
91
+ end
92
+
93
+ def logger
94
+ Honeybadger.logger
95
+ end
96
+
97
+ def setup_http_connection
98
+ http =
99
+ Net::HTTP::Proxy(proxy_host, proxy_port, proxy_user, proxy_pass).
100
+ new(url.host, url.port)
101
+
102
+ http.read_timeout = http_read_timeout
103
+ http.open_timeout = http_open_timeout
104
+
105
+ if secure?
106
+ http.use_ssl = true
107
+
108
+ http.ca_file = Honeybadger.configuration.ca_bundle_path
109
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
110
+ else
111
+ http.use_ssl = false
112
+ end
113
+
114
+ http
115
+ rescue => e
116
+ log(:error, "[Honeybadger::Sender#setup_http_connection] Failure initializing the HTTP connection.\nError: #{e.class} - #{e.message}\nBacktrace:\n#{e.backtrace.join("\n\t")}")
117
+ raise e
118
+ end
119
+ end
120
+ end