jordan-brough-hoptoad_notifier 2.3.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 (45) hide show
  1. data/CHANGELOG +161 -0
  2. data/INSTALL +25 -0
  3. data/MIT-LICENSE +22 -0
  4. data/README.rdoc +384 -0
  5. data/Rakefile +217 -0
  6. data/SUPPORTED_RAILS_VERSIONS +9 -0
  7. data/TESTING.rdoc +8 -0
  8. data/generators/hoptoad/hoptoad_generator.rb +63 -0
  9. data/generators/hoptoad/lib/insert_commands.rb +34 -0
  10. data/generators/hoptoad/lib/rake_commands.rb +24 -0
  11. data/generators/hoptoad/templates/capistrano_hook.rb +6 -0
  12. data/generators/hoptoad/templates/hoptoad_notifier_tasks.rake +25 -0
  13. data/generators/hoptoad/templates/initializer.rb +6 -0
  14. data/lib/hoptoad_notifier.rb +148 -0
  15. data/lib/hoptoad_notifier/backtrace.rb +99 -0
  16. data/lib/hoptoad_notifier/capistrano.rb +20 -0
  17. data/lib/hoptoad_notifier/configuration.rb +232 -0
  18. data/lib/hoptoad_notifier/notice.rb +318 -0
  19. data/lib/hoptoad_notifier/rack.rb +40 -0
  20. data/lib/hoptoad_notifier/rails.rb +37 -0
  21. data/lib/hoptoad_notifier/rails/action_controller_catcher.rb +29 -0
  22. data/lib/hoptoad_notifier/rails/controller_methods.rb +63 -0
  23. data/lib/hoptoad_notifier/rails/error_lookup.rb +33 -0
  24. data/lib/hoptoad_notifier/rails3_tasks.rb +90 -0
  25. data/lib/hoptoad_notifier/railtie.rb +23 -0
  26. data/lib/hoptoad_notifier/sender.rb +63 -0
  27. data/lib/hoptoad_notifier/tasks.rb +97 -0
  28. data/lib/hoptoad_notifier/version.rb +3 -0
  29. data/lib/hoptoad_tasks.rb +44 -0
  30. data/lib/rails/generators/hoptoad/hoptoad_generator.rb +69 -0
  31. data/lib/templates/rescue.erb +91 -0
  32. data/rails/init.rb +1 -0
  33. data/script/integration_test.rb +38 -0
  34. data/test/backtrace_test.rb +118 -0
  35. data/test/catcher_test.rb +324 -0
  36. data/test/configuration_test.rb +208 -0
  37. data/test/helper.rb +239 -0
  38. data/test/hoptoad_tasks_test.rb +152 -0
  39. data/test/logger_test.rb +85 -0
  40. data/test/notice_test.rb +443 -0
  41. data/test/notifier_test.rb +222 -0
  42. data/test/rack_test.rb +58 -0
  43. data/test/rails_initializer_test.rb +36 -0
  44. data/test/sender_test.rb +123 -0
  45. metadata +205 -0
@@ -0,0 +1,40 @@
1
+ module HoptoadNotifier
2
+ # Middleware for Rack applications. Any errors raised by the upstream
3
+ # application will be delivered to Hoptoad and re-raised.
4
+ #
5
+ # Synopsis:
6
+ #
7
+ # require 'rack'
8
+ # require 'hoptoad_notifier'
9
+ #
10
+ # HoptoadNotifier.configure do |config|
11
+ # config.api_key = 'my_api_key'
12
+ # end
13
+ #
14
+ # app = Rack::Builder.app do
15
+ # use HoptoadNotifier::Rack
16
+ # run lambda { |env| raise "Rack down" }
17
+ # end
18
+ #
19
+ # Use a standard HoptoadNotifier.configure call to configure your api key.
20
+ class Rack
21
+ def initialize(app)
22
+ @app = app
23
+ end
24
+
25
+ def call(env)
26
+ begin
27
+ response = @app.call(env)
28
+ rescue Exception => raised
29
+ HoptoadNotifier.notify_or_ignore(raised, :rack_env => env)
30
+ raise
31
+ end
32
+
33
+ if env['rack.exception']
34
+ HoptoadNotifier.notify_or_ignore(env['rack.exception'], :rack_env => env)
35
+ end
36
+
37
+ response
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,37 @@
1
+ require 'hoptoad_notifier'
2
+ require 'hoptoad_notifier/rails/controller_methods'
3
+ require 'hoptoad_notifier/rails/action_controller_catcher'
4
+ require 'hoptoad_notifier/rails/error_lookup'
5
+
6
+ module HoptoadNotifier
7
+ module Rails
8
+ def self.initialize
9
+ if defined?(ActionController::Base)
10
+ ActionController::Base.send(:include, HoptoadNotifier::Rails::ActionControllerCatcher)
11
+ ActionController::Base.send(:include, HoptoadNotifier::Rails::ErrorLookup)
12
+ ActionController::Base.send(:include, HoptoadNotifier::Rails::ControllerMethods)
13
+ end
14
+
15
+ rails_logger = if defined?(::Rails.logger)
16
+ ::Rails.logger
17
+ elsif defined?(RAILS_DEFAULT_LOGGER)
18
+ RAILS_DEFAULT_LOGGER
19
+ end
20
+
21
+ if defined?(::Rails.configuration) && ::Rails.configuration.respond_to?(:middleware)
22
+ ::Rails.configuration.middleware.insert_after 'ActionController::Failsafe',
23
+ HoptoadNotifier::Rack
24
+ end
25
+
26
+ HoptoadNotifier.configure(true) do |config|
27
+ config.logger = rails_logger
28
+ config.environment_name = RAILS_ENV if defined?(RAILS_ENV)
29
+ config.project_root = RAILS_ROOT if defined?(RAILS_ROOT)
30
+ config.framework = "Rails: #{::Rails::VERSION::STRING}" if defined?(::Rails::VERSION)
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ HoptoadNotifier::Rails.initialize
37
+
@@ -0,0 +1,29 @@
1
+ module HoptoadNotifier
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_hoptoad, :rescue_action_in_public)
8
+ base.send(:alias_method, :rescue_action_in_public, :rescue_action_in_public_with_hoptoad)
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_hoptoad(exception)
16
+ unless hoptoad_ignore_user_agent?
17
+ HoptoadNotifier.notify_or_ignore(exception, hoptoad_request_data)
18
+ end
19
+ rescue_action_in_public_without_hoptoad(exception)
20
+ end
21
+
22
+ def hoptoad_ignore_user_agent? #:nodoc:
23
+ # Rails 1.2.6 doesn't have request.user_agent, so check for it here
24
+ user_agent = request.respond_to?(:user_agent) ? request.user_agent : request.env["HTTP_USER_AGENT"]
25
+ HoptoadNotifier.configuration.ignore_user_agent.flatten.any? { |ua| ua === user_agent }
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,63 @@
1
+ module HoptoadNotifier
2
+ module Rails
3
+ module ControllerMethods
4
+ private
5
+
6
+ # This method should be used for sending manual notifications while you are still
7
+ # inside the controller. Otherwise it works like HoptoadNotifier.notify.
8
+ def notify_hoptoad(hash_or_exception)
9
+ unless consider_all_requests_local || local_request?
10
+ HoptoadNotifier.notify(hash_or_exception, hoptoad_request_data)
11
+ end
12
+ end
13
+
14
+ def hoptoad_ignore_user_agent? #:nodoc:
15
+ # Rails 1.2.6 doesn't have request.user_agent, so check for it here
16
+ user_agent = request.respond_to?(:user_agent) ? request.user_agent : request.env["HTTP_USER_AGENT"]
17
+ HoptoadNotifier.configuration.ignore_user_agent.flatten.any? { |ua| ua === user_agent }
18
+ end
19
+
20
+ def hoptoad_request_data
21
+ { :parameters => hoptoad_filter_if_filtering(params.to_hash),
22
+ :session_data => hoptoad_session_data,
23
+ :controller => params[:controller],
24
+ :action => params[:action],
25
+ :url => hoptoad_request_url,
26
+ :cgi_data => hoptoad_filter_if_filtering(request.env).merge(hoptoad_extra_cgi_data) }
27
+ end
28
+
29
+ # override in your controllers to provide extra hoptoad cgi data
30
+ def hoptoad_extra_cgi_data
31
+ {}
32
+ end
33
+
34
+ def hoptoad_filter_if_filtering(hash)
35
+ if respond_to?(:filter_parameters)
36
+ filter_parameters(hash) rescue hash
37
+ else
38
+ hash
39
+ end
40
+ end
41
+
42
+ def hoptoad_session_data
43
+ if session.respond_to?(:to_hash)
44
+ session.to_hash
45
+ else
46
+ session.data
47
+ end
48
+ end
49
+
50
+ def hoptoad_request_url
51
+ url = "#{request.protocol}#{request.host}"
52
+
53
+ unless [80, 443].include?(request.port)
54
+ url << ":#{request.port}"
55
+ end
56
+
57
+ url << request.request_uri
58
+ url
59
+ end
60
+ end
61
+ end
62
+ end
63
+
@@ -0,0 +1,33 @@
1
+ module HoptoadNotifier
2
+ module Rails
3
+ module ErrorLookup
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_locally_without_hoptoad, :rescue_action_locally)
8
+ base.send(:alias_method, :rescue_action_locally, :rescue_action_locally_with_hoptoad)
9
+ end
10
+
11
+ private
12
+
13
+ def rescue_action_locally_with_hoptoad(exception)
14
+ result = rescue_action_locally_without_hoptoad(exception)
15
+
16
+ if HoptoadNotifier.configuration.development_lookup
17
+ path = File.join(File.dirname(__FILE__), '..', '..', 'templates', 'rescue.erb')
18
+ notice = HoptoadNotifier.build_lookup_hash_for(exception, hoptoad_request_data)
19
+
20
+ result << @template.render(
21
+ :file => path,
22
+ :use_full_path => false,
23
+ :locals => { :host => HoptoadNotifier.configuration.host,
24
+ :api_key => HoptoadNotifier.configuration.api_key,
25
+ :notice => notice })
26
+ end
27
+
28
+ result
29
+ end
30
+ end
31
+ end
32
+ end
33
+
@@ -0,0 +1,90 @@
1
+ require 'hoptoad_notifier'
2
+
3
+ namespace :hoptoad do
4
+ desc "Notify Hoptoad of a new deploy."
5
+ task :deploy => :environment do
6
+ require 'hoptoad_tasks'
7
+ HoptoadTasks.deploy(:rails_env => ENV['TO'],
8
+ :scm_revision => ENV['REVISION'],
9
+ :scm_repository => ENV['REPO'],
10
+ :local_username => ENV['USER'],
11
+ :api_key => ENV['API_KEY'])
12
+ end
13
+
14
+ desc "Verify your gem installation by sending a test exception to the hoptoad service"
15
+ task :test => [:environment] do
16
+ Rails.logger = Logger.new(STDOUT)
17
+ Rails.logger.level = Logger::DEBUG
18
+ HoptoadNotifier.configure(true) do |config|
19
+ config.logger = Rails.logger
20
+ end
21
+
22
+ require 'app/controllers/application_controller'
23
+
24
+ class HoptoadTestingException < RuntimeError; end
25
+
26
+ unless HoptoadNotifier.configuration.api_key
27
+ puts "Hoptoad needs an API key configured! Check the README to see how to add it."
28
+ exit
29
+ end
30
+
31
+ HoptoadNotifier.configuration.development_environments = []
32
+
33
+ puts "Configuration:"
34
+ HoptoadNotifier.configuration.to_hash.each do |key, value|
35
+ puts sprintf("%25s: %s", key.to_s, value.inspect.slice(0, 55))
36
+ end
37
+
38
+ unless defined?(ApplicationController)
39
+ puts "No ApplicationController found"
40
+ exit
41
+ end
42
+
43
+ puts 'Setting up the Controller.'
44
+ class ApplicationController
45
+ # This is to bypass any filters that may prevent access to the action.
46
+ prepend_before_filter :test_hoptoad
47
+ def test_hoptoad
48
+ puts "Raising '#{exception_class.name}' to simulate application failure."
49
+ raise exception_class.new, 'Testing hoptoad via "rake hoptoad:test". If you can see this, it works.'
50
+ end
51
+
52
+ # def rescue_action(exception)
53
+ # rescue_action_in_public exception
54
+ # end
55
+
56
+ # Ensure we actually have an action to go to.
57
+ def verify; end
58
+
59
+ # def consider_all_requests_local
60
+ # false
61
+ # end
62
+
63
+ # def local_request?
64
+ # false
65
+ # end
66
+
67
+ def exception_class
68
+ exception_name = ENV['EXCEPTION'] || "HoptoadTestingException"
69
+ Object.const_get(exception_name)
70
+ rescue
71
+ Object.const_set(exception_name, Class.new(Exception))
72
+ end
73
+
74
+ def logger
75
+ nil
76
+ end
77
+ end
78
+ class HoptoadVerificationController < ApplicationController; end
79
+
80
+ Rails::Application.routes_reloader.reload_if_changed
81
+ Rails::Application.routes.draw do |map|
82
+ match 'verify' => 'application#verify', :as => 'verify'
83
+ end
84
+
85
+ puts 'Processing request.'
86
+ env = Rack::MockRequest.env_for("/verify")
87
+ Rails::Application.call(env)
88
+ end
89
+ end
90
+
@@ -0,0 +1,23 @@
1
+ require 'hoptoad_notifier'
2
+ require 'rails'
3
+
4
+ module HoptoadNotifier
5
+ class Railtie < Rails::Railtie
6
+ rake_tasks do
7
+ require "hoptoad_notifier/rails3_tasks"
8
+ end
9
+
10
+ initializer "hoptoad.use_rack_middleware" do |app|
11
+ app.config.middleware.use "HoptoadNotifier::Rack"
12
+ end
13
+
14
+ config.after_initialize do
15
+ HoptoadNotifier.configure(true) do |config|
16
+ config.logger = Rails.logger
17
+ config.environment_name = Rails.env
18
+ config.project_root = Rails.root
19
+ config.framework = "Rails: #{::Rails::VERSION::STRING}"
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,63 @@
1
+ module HoptoadNotifier
2
+ # Sends out the notice to Hoptoad
3
+ class Sender
4
+
5
+ NOTICES_URI = '/notifier_api/v2/notices/'.freeze
6
+
7
+ def initialize(options = {})
8
+ [:proxy_host, :proxy_port, :proxy_user, :proxy_pass, :protocol,
9
+ :host, :port, :secure, :http_open_timeout, :http_read_timeout].each do |option|
10
+ instance_variable_set("@#{option}", options[option])
11
+ end
12
+ end
13
+
14
+ # Sends the notice data off to Hoptoad for processing.
15
+ #
16
+ # @param [String] data The XML notice to be sent off
17
+ def send_to_hoptoad(data)
18
+ logger.debug { "Sending request to #{url.to_s}:\n#{data}" } if logger
19
+
20
+ http =
21
+ Net::HTTP::Proxy(proxy_host, proxy_port, proxy_user, proxy_pass).
22
+ new(url.host, url.port)
23
+
24
+ http.read_timeout = http_read_timeout
25
+ http.open_timeout = http_open_timeout
26
+ http.use_ssl = secure
27
+
28
+ response = begin
29
+ http.post(url.path, data, HEADERS)
30
+ rescue TimeoutError => e
31
+ log :error, "Timeout while contacting the Hoptoad server."
32
+ nil
33
+ end
34
+
35
+ case response
36
+ when Net::HTTPSuccess then
37
+ log :info, "Success: #{response.class}", response
38
+ else
39
+ log :error, "Failure: #{response.class}", response
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ attr_reader :proxy_host, :proxy_port, :proxy_user, :proxy_pass, :protocol,
46
+ :host, :port, :secure, :http_open_timeout, :http_read_timeout
47
+
48
+ def url
49
+ URI.parse("#{protocol}://#{host}:#{port}").merge(NOTICES_URI)
50
+ end
51
+
52
+ def log(level, message, response = nil)
53
+ logger.send level, LOG_PREFIX + message if logger
54
+ HoptoadNotifier.report_environment_info
55
+ HoptoadNotifier.report_response_body(response.body) if response && response.respond_to?(:body)
56
+ end
57
+
58
+ def logger
59
+ HoptoadNotifier.logger
60
+ end
61
+
62
+ end
63
+ end
@@ -0,0 +1,97 @@
1
+ require 'hoptoad_notifier'
2
+
3
+ namespace :hoptoad do
4
+ desc "Notify Hoptoad of a new deploy."
5
+ task :deploy => :environment do
6
+ require 'hoptoad_tasks'
7
+ HoptoadTasks.deploy(:rails_env => ENV['TO'],
8
+ :scm_revision => ENV['REVISION'],
9
+ :scm_repository => ENV['REPO'],
10
+ :local_username => ENV['USER'],
11
+ :api_key => ENV['API_KEY'])
12
+ end
13
+
14
+ task :log_stdout do
15
+ require 'logger'
16
+ RAILS_DEFAULT_LOGGER = Logger.new(STDOUT)
17
+ end
18
+
19
+ desc "Verify your gem installation by sending a test exception to the hoptoad service"
20
+ task :test => ['hoptoad:log_stdout', :environment] do
21
+ RAILS_DEFAULT_LOGGER.level = Logger::DEBUG
22
+
23
+ require 'action_controller/test_process'
24
+
25
+ Dir["app/controllers/application*.rb"].each { |file| require(file) }
26
+
27
+ class HoptoadTestingException < RuntimeError; end
28
+
29
+ unless HoptoadNotifier.configuration.api_key
30
+ puts "Hoptoad needs an API key configured! Check the README to see how to add it."
31
+ exit
32
+ end
33
+
34
+ HoptoadNotifier.configuration.development_environments = []
35
+
36
+ catcher = HoptoadNotifier::Rails::ActionControllerCatcher
37
+ in_controller = ApplicationController.included_modules.include?(catcher)
38
+ in_base = ActionController::Base.included_modules.include?(catcher)
39
+ if !in_controller || !in_base
40
+ puts "Rails initialization did not occur"
41
+ exit
42
+ end
43
+
44
+ puts "Configuration:"
45
+ HoptoadNotifier.configuration.to_hash.each do |key, value|
46
+ puts sprintf("%25s: %s", key.to_s, value.inspect.slice(0, 55))
47
+ end
48
+
49
+ unless defined?(ApplicationController)
50
+ puts "No ApplicationController found"
51
+ exit
52
+ end
53
+
54
+ puts 'Setting up the Controller.'
55
+ class ApplicationController
56
+ # This is to bypass any filters that may prevent access to the action.
57
+ prepend_before_filter :test_hoptoad
58
+ def test_hoptoad
59
+ puts "Raising '#{exception_class.name}' to simulate application failure."
60
+ raise exception_class.new, 'Testing hoptoad via "rake hoptoad:test". If you can see this, it works.'
61
+ end
62
+
63
+ def rescue_action(exception)
64
+ rescue_action_in_public exception
65
+ end
66
+
67
+ # Ensure we actually have an action to go to.
68
+ def verify; end
69
+
70
+ def consider_all_requests_local
71
+ false
72
+ end
73
+
74
+ def local_request?
75
+ false
76
+ end
77
+
78
+ def exception_class
79
+ exception_name = ENV['EXCEPTION'] || "HoptoadTestingException"
80
+ Object.const_get(exception_name)
81
+ rescue
82
+ Object.const_set(exception_name, Class.new(Exception))
83
+ end
84
+
85
+ def logger
86
+ nil
87
+ end
88
+ end
89
+ class HoptoadVerificationController < ApplicationController; end
90
+
91
+ puts 'Processing request.'
92
+ request = ActionController::TestRequest.new
93
+ response = ActionController::TestResponse.new
94
+ HoptoadVerificationController.new.process(request, response)
95
+ end
96
+ end
97
+