pboling-super_exception_notifier 1.6.5

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 (38) hide show
  1. data/MIT-LICENSE +21 -0
  2. data/README.rdoc +419 -0
  3. data/VERSION.yml +4 -0
  4. data/exception_notification.gemspec +71 -0
  5. data/init.rb +1 -0
  6. data/lib/exception_notifiable.rb +278 -0
  7. data/lib/exception_notifier.rb +108 -0
  8. data/lib/exception_notifier_helper.rb +58 -0
  9. data/lib/hooks_notifier.rb +53 -0
  10. data/lib/notifiable.rb +8 -0
  11. data/lib/super_exception_notifier/custom_exception_classes.rb +16 -0
  12. data/lib/super_exception_notifier/custom_exception_methods.rb +50 -0
  13. data/rails/app/views/exception_notifiable/400.html +5 -0
  14. data/rails/app/views/exception_notifiable/403.html +6 -0
  15. data/rails/app/views/exception_notifiable/404.html +6 -0
  16. data/rails/app/views/exception_notifiable/405.html +6 -0
  17. data/rails/app/views/exception_notifiable/410.html +7 -0
  18. data/rails/app/views/exception_notifiable/418.html +6 -0
  19. data/rails/app/views/exception_notifiable/422.html +5 -0
  20. data/rails/app/views/exception_notifiable/423.html +6 -0
  21. data/rails/app/views/exception_notifiable/501.html +8 -0
  22. data/rails/app/views/exception_notifiable/503.html +6 -0
  23. data/rails/init.rb +18 -0
  24. data/test/exception_notifier_helper_test.rb +76 -0
  25. data/test/exception_notify_functional_test.rb +102 -0
  26. data/test/mocks/404.html +1 -0
  27. data/test/mocks/500.html +1 -0
  28. data/test/mocks/controllers.rb +46 -0
  29. data/test/test_helper.rb +28 -0
  30. data/views/exception_notifier/_backtrace.html.erb +1 -0
  31. data/views/exception_notifier/_environment.html.erb +14 -0
  32. data/views/exception_notifier/_inspect_model.html.erb +16 -0
  33. data/views/exception_notifier/_request.html.erb +8 -0
  34. data/views/exception_notifier/_session.html.erb +7 -0
  35. data/views/exception_notifier/_title.html.erb +3 -0
  36. data/views/exception_notifier/background_exception_notification.text.plain.erb +6 -0
  37. data/views/exception_notifier/exception_notification.text.plain.erb +10 -0
  38. metadata +100 -0
@@ -0,0 +1,53 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+
4
+ module HooksNotifier
5
+ # Deliver exception data hash to web hooks, if any
6
+ #
7
+ def self.deliver_exception_to_web_hooks(config, exception, controller, request, data={}, the_blamed = nil)
8
+ params = build_web_hook_params(config, exception, controller, request, data, the_blamed)
9
+ # TODO: use threads here
10
+ config[:web_hooks].each do |address|
11
+ post_hook(params, address)
12
+ end
13
+ end
14
+
15
+
16
+ # Parameters hash based on Merb Exceptions example
17
+ #
18
+ def self.build_web_hook_params(config, exception, controller, request, data={}, the_blamed = nil)
19
+ host = (request.env["HTTP_X_FORWARDED_HOST"] || request.env["HTTP_HOST"])
20
+ p = {
21
+ 'environment' => (defined?(Rails) ? Rails.env : RAILS_ENV),
22
+ 'exceptions' => [{
23
+ :class => exception.class.to_s,
24
+ :backtrace => exception.backtrace,
25
+ :message => exception.message
26
+ }],
27
+ 'app_name' => config[:app_name],
28
+ 'version' => config[:version],
29
+ 'blame' => "#{the_blamed}"
30
+ }
31
+ if !request.nil?
32
+ p.merge!({'request_url' => "#{request.protocol}#{host}#{request.request_uri}"})
33
+ p.merge!({'request_action' => request.parameters['action']})
34
+ p.merge!({'request_params' => request.parameters.inspect})
35
+ end
36
+ p.merge!({'request_controller' => controller.class.name}) if !controller.nil?
37
+ p.merge!({'status' => exception.status}) if exception.respond_to?(:status)
38
+ return p
39
+ end
40
+
41
+ def self.post_hook(params, address)
42
+ uri = URI.parse(address)
43
+ uri.path = '/' if uri.path=='' # set a path if one isn't provided to keep Net::HTTP happy
44
+
45
+ headers = { 'Content-Type' => 'text/x-json' }
46
+ data = params.to_json
47
+ Net::HTTP.start(uri.host, uri.port) do |http|
48
+ http.request_post(uri.path, data, headers)
49
+ end
50
+ data
51
+ end
52
+
53
+ end
data/lib/notifiable.rb ADDED
@@ -0,0 +1,8 @@
1
+ module Notifiable
2
+ def notifiable(&block)
3
+ yield
4
+ rescue => exception
5
+ ExceptionNotifier.deliver_exception_notification(exception)
6
+ raise
7
+ end
8
+ end
@@ -0,0 +1,16 @@
1
+ #Copyright (c) 2008-2009 Peter H. Boling of 9thBit LLC
2
+ #Released under the MIT license
3
+
4
+ module SuperExceptionNotifier
5
+ module CustomExceptionClasses
6
+
7
+ class AccessDenied < StandardError; end
8
+ class ResourceGone < StandardError; end
9
+ class NotImplemented < StandardError; end
10
+ class PageNotFound < StandardError; end
11
+ class InvalidMethod < StandardError; end
12
+ class CorruptData < StandardError; end
13
+ class MethodDisabled < StandardError; end
14
+
15
+ end
16
+ end
@@ -0,0 +1,50 @@
1
+ #Copyright (c) 2008-2009 Peter H. Boling of 9thBit LLC
2
+ #Released under the MIT license
3
+
4
+ module SuperExceptionNotifier
5
+ module CustomExceptionMethods
6
+
7
+ #For a while after disabling a route/URL that had been functional we should set it to resource gone to inform people to remove bookmarks.
8
+ def resource_gone
9
+ raise ResourceGone
10
+ end
11
+ #Then for things that have never existed or have not for a long time we call not_implemented
12
+ def not_implemented
13
+ raise NotImplemented
14
+ end
15
+ #Resources that must be requested with a specific HTTP Meethod (GET, PUT, POST, DELETE, AJAX, etc) but are requested otherwise should:
16
+ def invalid_method
17
+ raise InvalidMethod
18
+ end
19
+ #If your ever at a spot in the code that should never get reached, but corrupt data might get you there anyways then this is for you:
20
+ def corrupt_data
21
+ raise CorruptData
22
+ end
23
+ def page_not_found
24
+ raise PageNotFound
25
+ end
26
+ def record_not_found
27
+ raise ActiveRecord::RecordNotFound
28
+ end
29
+ def method_disabled
30
+ raise MethodDisabled
31
+ end
32
+ #The current user does not have enough privileges to access the requested resource
33
+ def access_denied
34
+ raise AccessDenied
35
+ end
36
+
37
+ protected
38
+
39
+ def generic_error
40
+ error_stickie("Sorry, an error has occurred.")
41
+ corrupt_data
42
+ end
43
+
44
+ def invalid_page
45
+ error_stickie("Sorry, the page number you requested was not valid.")
46
+ page_not_found
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,5 @@
1
+ <!-- This file lives in gems/super_exception_notifier/rails/app/views/exception_notifiable/400.html. See Readme for instructions on customizing. -->
2
+ <div class="dialog">
3
+ <h1>Unable to Process Requested Data</h1>
4
+ <p>Reloading the page will probably not help.</p>
5
+ </div>
@@ -0,0 +1,6 @@
1
+ <!-- This file lives in gems/super_exception_notifier/rails/app/views/exception_notifiable/403.html. See Readme for instructions on customizing. -->
2
+ <div class="dialog">
3
+ <h1>Access Restricted</h1>
4
+
5
+ <p>If you need access to this resource please contact support or an administrator.</p>
6
+ </div>
@@ -0,0 +1,6 @@
1
+ <!-- This file lives in gems/super_exception_notifier/rails/app/views/exception_notifiable/404.html. See Readme for instructions on customizing. -->
2
+ <div class="dialog">
3
+ <h1>The page you were looking for doesn't exist.</h1>
4
+
5
+ <p>You may have mistyped the address or the page may have moved.</p>
6
+ </div>
@@ -0,0 +1,6 @@
1
+ <!-- This file lives in gems/super_exception_notifier/rails/app/views/exception_notifiable/404.html. See Readme for instructions on customizing. -->
2
+ <div class="dialog">
3
+ <h1>Invalid Method.</h1>
4
+
5
+ <p>This resource only accepts certain HTTP methods.</p>
6
+ </div>
@@ -0,0 +1,7 @@
1
+ <!-- This file lives in gems/super_exception_notifier/rails/app/views/exception_notifiable/410.html. See Readme for instructions on customizing. -->
2
+ <div class="dialog">
3
+ <h1>Requested resource is no longer available</h1>
4
+
5
+ <p>Please remove bookmarks to this resource.</p>
6
+ <p>If you arrived here via a link from somewhere else please inform them of the broken link.</p>
7
+ </div>
@@ -0,0 +1,6 @@
1
+ <!-- This file lives in gems/super_exception_notifier/rails/app/views/exception_notifiable/418.html. See Readme for instructions on customizing. -->
2
+ <div class="dialog">
3
+ <h1>418 I�m a teapot</h1>
4
+
5
+ <p>I MAY be short and stout. Defined by the April Fools specification RFC 2324. See Hyper Text Coffee Pot Control Protocol for more information.</p>
6
+ </div>
@@ -0,0 +1,5 @@
1
+ <!-- This file lives in gems/super_exception_notifier/rails/app/views/exception_notifiable/422.html. See Readme for instructions on customizing. -->
2
+ <div class="dialog">
3
+ <h1>Unprocessable Request</h1>
4
+ <p>The request was well-formed but was unable to be followed due to semantic errors.</p>
5
+ </div>
@@ -0,0 +1,6 @@
1
+ <!-- This file lives in gems/super_exception_notifier/rails/app/views/exception_notifiable/423.html. See Readme for instructions on customizing. -->
2
+ <div class="dialog">
3
+ <h1>The change you wanted was rejected because the resource is locked</h1>
4
+ <p>It might become available again soon, if you try again!</p>
5
+ <p>Maybe you tried to change something you didn't have access to.</p>
6
+ </div>
@@ -0,0 +1,8 @@
1
+ <!-- This file lives in gems/super_exception_notifier/rails/app/views/exception_notifiable/501.html. See Readme for instructions on customizing. -->
2
+ <div class="dialog">
3
+ <h1>Not Implemented</h1>
4
+
5
+ <p>Please remove bookmarks to this resource.</p>
6
+ <p>You have tried to access a feature that has not been implemented.</p>
7
+ <p>If you arrived here via a link from somewhere else please inform them of the broken link.</p>
8
+ </div>
@@ -0,0 +1,6 @@
1
+ <!-- This file lives in gems/super_exception_notifier/rails/app/views/exception_notifiable/503.html. See Readme for instructions on customizing. -->
2
+ <div class="dialog">
3
+ <h1>The server is temporarily unavailable.</h1>
4
+
5
+ <p>The resource you requested is temporarilly unavailable due to server overload, maintenance, or other downtime. Generally, this is a temporary state.</p>
6
+ </div>
data/rails/init.rb ADDED
@@ -0,0 +1,18 @@
1
+ require "action_mailer"
2
+
3
+ require File.join(File.dirname(__FILE__), '..', 'lib', "super_exception_notifier", "custom_exception_classes")
4
+ require File.join(File.dirname(__FILE__), '..', 'lib', "super_exception_notifier", "custom_exception_methods")
5
+
6
+ $:.unshift "#{File.dirname(__FILE__)}/lib"
7
+
8
+ require "hooks_notifier"
9
+ require "exception_notifier"
10
+ require "exception_notifiable"
11
+ require "exception_notifier_helper"
12
+ require "notifiable"
13
+
14
+ Object.class_eval do include Notifiable end
15
+
16
+ if ActionController::Base.respond_to?(:append_view_path)
17
+ ActionController::Base.append_view_path(File.join(File.dirname(__FILE__), 'app', 'views'))
18
+ end
@@ -0,0 +1,76 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/test_helper')
2
+ require 'exception_notifier_helper'
3
+
4
+ class ExceptionNotifierHelperTest < Test::Unit::TestCase
5
+
6
+ class ExceptionNotifierHelperIncludeTarget
7
+ include ExceptionNotifierHelper
8
+ end
9
+
10
+ def setup
11
+ @helper = ExceptionNotifierHelperIncludeTarget.new
12
+ end
13
+
14
+ # No controller
15
+
16
+ def test_should_not_exclude_raw_post_parameters_if_no_controller
17
+ assert !@helper.exclude_raw_post_parameters?
18
+ end
19
+
20
+ # Controller, no filtering
21
+
22
+ class ControllerWithoutFilterParameters; end
23
+
24
+ def test_should_not_filter_env_values_for_raw_post_data_keys_if_controller_can_not_filter_parameters
25
+ stub_controller(ControllerWithoutFilterParameters.new)
26
+ assert @helper.filter_sensitive_post_data_from_env("RAW_POST_DATA", "secret").include?("secret")
27
+ end
28
+ def test_should_not_exclude_raw_post_parameters_if_controller_can_not_filter_parameters
29
+ stub_controller(ControllerWithoutFilterParameters.new)
30
+ assert !@helper.exclude_raw_post_parameters?
31
+ end
32
+ def test_should_return_params_if_controller_can_not_filter_parameters
33
+ stub_controller(ControllerWithoutFilterParameters.new)
34
+ assert_equal :params, @helper.filter_sensitive_post_data_parameters(:params)
35
+ end
36
+
37
+ # Controller with filter paramaters method, no params to filter
38
+
39
+ class ControllerWithFilterParametersThatDoesntFilter
40
+ def filter_parameters(params); params end
41
+ end
42
+
43
+ def test_should_filter_env_values_for_raw_post_data_keys_if_controller_can_filter_parameters
44
+ stub_controller(ControllerWithFilterParametersThatDoesntFilter.new)
45
+ assert !@helper.filter_sensitive_post_data_from_env("RAW_POST_DATA", "secret").include?("secret")
46
+ assert @helper.filter_sensitive_post_data_from_env("SOME_OTHER_KEY", "secret").include?("secret")
47
+ end
48
+ def test_should_exclude_raw_post_parameters_if_controller_can_filter_parameters
49
+ stub_controller(ControllerWithFilterParametersThatDoesntFilter.new)
50
+ assert @helper.exclude_raw_post_parameters?
51
+ end
52
+
53
+ # Controller with filter paramaters method, filtering a secret param
54
+
55
+ class ControllerWithFilterParametersThatDoesFilter
56
+ def filter_parameters(params); :filtered end
57
+ end
58
+
59
+ def test_should_delegate_param_filtering_to_controller_if_controller_can_filter_parameters
60
+ stub_controller(ControllerWithFilterParametersThatDoesFilter.new)
61
+ assert_equal :filtered, @helper.filter_sensitive_post_data_parameters(:secret)
62
+ end
63
+
64
+ def test_compat_mode_constant
65
+ if defined?(RAILS_GEM_VERSION)
66
+ assert_equal(ExceptionNotifierHelper::COMPAT_MODE, RAILS_GEM_VERSION >= 2)
67
+ else
68
+ assert_equal(ExceptionNotifierHelper::COMPAT_MODE, false)
69
+ end
70
+ end
71
+
72
+ private
73
+ def stub_controller(controller)
74
+ @helper.instance_variable_set(:@controller, controller)
75
+ end
76
+ end
@@ -0,0 +1,102 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/test_helper')
2
+ require 'test/unit'
3
+
4
+ RAILS_DEFAULT_LOGGER = Logger.new(nil)
5
+
6
+ require File.join(File.dirname(__FILE__), 'mocks/controllers')
7
+
8
+ ActionController::Routing::Routes.clear!
9
+ ActionController::Routing::Routes.draw {|m| m.connect ':controller/:action/:id' }
10
+
11
+ class ExceptionNotifyFunctionalTest < ActionController::TestCase
12
+
13
+ def setup
14
+ @request = ActionController::TestRequest.new
15
+ @response = ActionController::TestResponse.new
16
+ ActionController::Base.consider_all_requests_local = false
17
+ @@delivered_mail = []
18
+ ActionMailer::Base.class_eval do
19
+ def deliver!(mail = @mail)
20
+ @@delivered_mail << mail
21
+ end
22
+ end
23
+ end
24
+
25
+ def test_view_path_200; assert_view_path_string("200"); end
26
+ def test_view_path_400; assert_view_path_string("400"); end
27
+ def test_view_path_403; assert_view_path_string("403"); end
28
+ def test_view_path_404; assert_view_path_string("404"); end
29
+ def test_view_path_405; assert_view_path_string("405"); end
30
+ def test_view_path_410; assert_view_path_string("410"); end
31
+ def test_view_path_418; assert_view_path_string("422"); end
32
+ def test_view_path_422; assert_view_path_string("422"); end
33
+ def test_view_path_423; assert_view_path_string("423"); end
34
+ def test_view_path_500; assert_view_path_string("500"); end
35
+ def test_view_path_501; assert_view_path_string("501"); end
36
+ def test_view_path_503; assert_view_path_string("503"); end
37
+ def test_view_path_nil; assert_view_path_string(nil); end
38
+ def test_view_path_empty; assert_view_path_string(""); end
39
+ def test_view_path_nonsense; assert_view_path_string("slartibartfarst"); end
40
+
41
+ def test_old_style_where_requests_are_local
42
+ ActionController::Base.consider_all_requests_local = true
43
+ @controller = OldStyle.new
44
+ get "runtime_error"
45
+
46
+ assert_nothing_mailed
47
+ end
48
+
49
+ def test_new_style_where_requests_are_local
50
+ ActionController::Base.consider_all_requests_local = true
51
+ @controller = NewStyle.new
52
+ get "runtime_error"
53
+
54
+ # puts @response.body
55
+ assert_nothing_mailed
56
+ end
57
+
58
+ def test_old_style_runtime_error_sends_mail
59
+ @controller = OldStyle.new
60
+ get "runtime_error"
61
+ assert_error_mail_contains("This is a runtime error that we should be emailed about")
62
+ end
63
+
64
+ def test_old_style_record_not_found_does_not_send_mail
65
+ @controller = OldStyle.new
66
+ get "record_not_found"
67
+ assert_nothing_mailed
68
+ end
69
+
70
+ def test_new_style_runtime_error_sends_mail
71
+ @controller = NewStyle.new
72
+ get "runtime_error"
73
+ assert_error_mail_contains("This is a runtime error that we should be emailed about")
74
+ end
75
+
76
+ def test_new_style_record_not_found_does_not_send_mail
77
+ @controller = NewStyle.new
78
+ get "record_not_found"
79
+ assert_nothing_mailed
80
+ end
81
+
82
+ private
83
+
84
+ def assert_view_path_string(status)
85
+ assert(ExceptionNotifier.get_view_path(status).is_a?(String), "View Path is not a string for status code '#{status}'")
86
+ end
87
+
88
+ def assert_error_mail_contains(text)
89
+ assert(mailed_error.index(text),
90
+ "Expected mailed error body to contain '#{text}', but not found. \n actual contents: \n#{mailed_error}")
91
+ end
92
+
93
+ def assert_nothing_mailed
94
+ assert @@delivered_mail.empty?, "Expected to have NOT mailed out a notification about an error occuring, but mailed: \n#{@@delivered_mail}"
95
+ end
96
+
97
+ def mailed_error
98
+ assert @@delivered_mail.last, "Expected to have mailed out a notification about an error occuring, but none mailed"
99
+ @@delivered_mail.last.encoded
100
+ end
101
+
102
+ end
@@ -0,0 +1 @@
1
+ simulate 404 error file in public
@@ -0,0 +1 @@
1
+ simulate 500 error file in public
@@ -0,0 +1,46 @@
1
+ module Rails
2
+ def self.public_path
3
+ File.dirname(__FILE__)
4
+ end
5
+
6
+ def self.env
7
+ 'test'
8
+ end
9
+ end
10
+
11
+ class Application < ActionController::Base
12
+
13
+ def runtime_error
14
+ raise "This is a runtime error that we should be emailed about"
15
+ end
16
+
17
+ def record_not_found
18
+ raise ActiveRecord::RecordNotFound
19
+ end
20
+
21
+ def local_request?
22
+ false
23
+ end
24
+
25
+ end
26
+
27
+ class OldStyle < Application
28
+ include ExceptionNotifiable
29
+ self.exception_notifier_verbose = false
30
+ end
31
+
32
+ class SpecialErrorThing < RuntimeError
33
+ end
34
+
35
+ class NewStyle < Application
36
+ include ExceptionNotifiable
37
+ self.exception_notifier_verbose = false
38
+
39
+ rescue_from ActiveRecord::RecordNotFound do |exception|
40
+ render :text => "404", :status => 404
41
+ end
42
+
43
+ rescue_from RuntimeError do |exception|
44
+ render :text => "500", :status => 500
45
+ end
46
+ end