honeybadger 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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