super_exception_notifier 2.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 (44) hide show
  1. data/MIT-LICENSE +21 -0
  2. data/README.rdoc +528 -0
  3. data/VERSION.yml +4 -0
  4. data/init.rb +1 -0
  5. data/lib/exception_notifiable.rb +175 -0
  6. data/lib/exception_notifier.rb +185 -0
  7. data/lib/exception_notifier_helper.rb +60 -0
  8. data/lib/notifiable.rb +91 -0
  9. data/lib/super_exception_notifier/custom_exception_classes.rb +16 -0
  10. data/lib/super_exception_notifier/custom_exception_methods.rb +50 -0
  11. data/lib/super_exception_notifier/deprecated_methods.rb +60 -0
  12. data/lib/super_exception_notifier/git_blame.rb +52 -0
  13. data/lib/super_exception_notifier/helpful_hashes.rb +66 -0
  14. data/lib/super_exception_notifier/hooks_notifier.rb +55 -0
  15. data/lib/super_exception_notifier/notifiable_helper.rb +79 -0
  16. data/rails/app/views/exception_notifiable/400.html +5 -0
  17. data/rails/app/views/exception_notifiable/403.html +6 -0
  18. data/rails/app/views/exception_notifiable/404.html +6 -0
  19. data/rails/app/views/exception_notifiable/405.html +6 -0
  20. data/rails/app/views/exception_notifiable/410.html +7 -0
  21. data/rails/app/views/exception_notifiable/418.html +6 -0
  22. data/rails/app/views/exception_notifiable/422.html +5 -0
  23. data/rails/app/views/exception_notifiable/423.html +6 -0
  24. data/rails/app/views/exception_notifiable/501.html +8 -0
  25. data/rails/app/views/exception_notifiable/503.html +6 -0
  26. data/rails/app/views/exception_notifiable/method_disabled.html.erb +6 -0
  27. data/rails/init.rb +25 -0
  28. data/tasks/notified_task.rake +15 -0
  29. data/test/exception_notifiable_test.rb +34 -0
  30. data/test/exception_notifier_helper_test.rb +76 -0
  31. data/test/exception_notifier_test.rb +41 -0
  32. data/test/exception_notify_functional_test.rb +139 -0
  33. data/test/mocks/controllers.rb +82 -0
  34. data/test/notifiable_test.rb +79 -0
  35. data/test/test_helper.rb +32 -0
  36. data/views/exception_notifier/_backtrace.html.erb +1 -0
  37. data/views/exception_notifier/_environment.html.erb +14 -0
  38. data/views/exception_notifier/_inspect_model.html.erb +16 -0
  39. data/views/exception_notifier/_request.html.erb +8 -0
  40. data/views/exception_notifier/_session.html.erb +6 -0
  41. data/views/exception_notifier/_title.html.erb +3 -0
  42. data/views/exception_notifier/background_exception_notification.text.plain.erb +10 -0
  43. data/views/exception_notifier/exception_notification.text.plain.erb +15 -0
  44. metadata +119 -0
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 2
3
+ :minor: 0
4
+ :patch: 0
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.dirname(__FILE__) + "/rails/init"
@@ -0,0 +1,175 @@
1
+ require 'ipaddr'
2
+
3
+ module ExceptionNotifiable
4
+ include SuperExceptionNotifier::NotifiableHelper
5
+
6
+ def self.included(base)
7
+ base.extend ClassMethods
8
+
9
+ # Adds the following class attributes to the classes that include ExceptionNotifiable
10
+ # HTTP status codes and what their 'English' status message is
11
+ base.cattr_accessor :http_status_codes
12
+ base.http_status_codes = HTTP_STATUS_CODES
13
+ # error_layout:
14
+ # can be defined at controller level to the name of the desired error layout,
15
+ # or set to true to render the controller's own default layout,
16
+ # or set to false to render errors with no layout
17
+ base.cattr_accessor :error_layout
18
+ base.error_layout = nil
19
+ # Rails error classes to rescue and how to rescue them (which error code to use)
20
+ base.cattr_accessor :error_class_status_codes
21
+ base.error_class_status_codes = self.codes_for_error_classes
22
+ # Verbosity of the gem
23
+ base.cattr_accessor :exception_notifiable_verbose
24
+ base.exception_notifiable_verbose = false
25
+ # Do Not Ever send error notification emails for these Error Classes
26
+ base.cattr_accessor :exception_notifiable_silent_exceptions
27
+ base.exception_notifiable_silent_exceptions = SILENT_EXCEPTIONS
28
+ # Notification Level
29
+ base.cattr_accessor :exception_notifiable_notification_level
30
+ base.exception_notifiable_notification_level = [:render, :email, :web_hooks]
31
+ end
32
+
33
+ module ClassMethods
34
+ include SuperExceptionNotifier::DeprecatedMethods
35
+
36
+ # specifies ip addresses that should be handled as though local
37
+ def consider_local(*args)
38
+ local_addresses.concat(args.flatten.map { |a| IPAddr.new(a) })
39
+ end
40
+
41
+ def local_addresses
42
+ addresses = read_inheritable_attribute(:local_addresses)
43
+ unless addresses
44
+ addresses = [IPAddr.new("127.0.0.1")]
45
+ write_inheritable_attribute(:local_addresses, addresses)
46
+ end
47
+ addresses
48
+ end
49
+
50
+ # set the exception_data deliverer OR retrieve the exception_data
51
+ def exception_data(deliverer = nil)
52
+ if deliverer
53
+ write_inheritable_attribute(:exception_data, deliverer)
54
+ else
55
+ read_inheritable_attribute(:exception_data)
56
+ end
57
+ end
58
+
59
+ def be_silent_for_exception?(exception)
60
+ self.exception_notifiable_silent_exceptions.respond_to?(:any?) && self.exception_notifiable_silent_exceptions.any? {|klass| klass === exception }
61
+ end
62
+
63
+ end
64
+
65
+ def be_silent_for_exception?(exception)
66
+ self.class.be_silent_for_exception?(exception)
67
+ end
68
+
69
+
70
+ private
71
+
72
+ def notification_level_sends_email?
73
+ self.class.exception_notifiable_notification_level.include?(:email)
74
+ end
75
+
76
+ def notification_level_sends_web_hooks?
77
+ self.class.exception_notifiable_notification_level.include?(:web_hooks)
78
+ end
79
+
80
+ def notification_level_renders?
81
+ self.class.exception_notifiable_notification_level.include?(:render)
82
+ end
83
+
84
+ # overrides Rails' local_request? method to also check any ip
85
+ # addresses specified through consider_local.
86
+ def local_request?
87
+ remote = IPAddr.new(request.remote_ip)
88
+ !self.class.local_addresses.detect { |addr| addr.include?(remote) }.nil?
89
+ end
90
+
91
+ # When the action being executed has its own local error handling (rescue)
92
+ # Or when the error accurs somewhere without a subsequent render (eg. method calls in console)
93
+ def rescue_with_handler(exception)
94
+ to_return = super
95
+ if to_return
96
+ verbose = self.class.exception_notifiable_verbose
97
+ puts "[RESCUE STYLE] rescue_with_handler" if verbose
98
+ data = get_exception_data
99
+ status_code = status_code_for_exception(exception)
100
+ #We only send email if it has been configured in environment
101
+ send_email = should_email_on_exception?(exception, status_code, verbose)
102
+ #We only send web hooks if they've been configured in environment
103
+ send_web_hooks = should_web_hook_on_exception?(exception, status_code, verbose)
104
+ the_blamed = ExceptionNotifier.config[:git_repo_path].nil? ? nil : lay_blame(exception)
105
+ verbose_output(exception, status_code, "rescued by handler", send_email, send_web_hooks, nil, the_blamed) if verbose
106
+ # Send the exception notificaiton email
107
+ perform_exception_notify_mailing(exception, data, nil, the_blamed, verbose) if send_email
108
+ # Send Web Hook requests
109
+ HooksNotifier.deliver_exception_to_web_hooks(ExceptionNotifier.config, exception, self, request, data, the_blamed) if send_web_hooks
110
+ end
111
+ to_return
112
+ end
113
+
114
+ # When the action being executed is letting SEN handle the exception completely
115
+ def rescue_action_in_public(exception)
116
+ # If the error class is NOT listed in the rails_errror_class hash then we get a generic 500 error:
117
+ # OTW if the error class is listed, but has a blank code or the code is == '200' then we get a custom error layout rendered
118
+ # OTW the error class is listed!
119
+ verbose = self.class.exception_notifiable_verbose
120
+ puts "[RESCUE STYLE] rescue_action_in_public" if verbose
121
+ status_code = status_code_for_exception(exception)
122
+ if status_code == '200'
123
+ notify_and_render_error_template(status_code, request, exception, ExceptionNotifier.get_view_path_for_class(exception, verbose), verbose)
124
+ else
125
+ notify_and_render_error_template(status_code, request, exception, ExceptionNotifier.get_view_path_for_status_code(status_code, verbose), verbose)
126
+ end
127
+ end
128
+
129
+ def notify_and_render_error_template(status_cd, request, exception, file_path, verbose = false)
130
+ status = self.class.http_status_codes[status_cd] ? status_cd + " " + self.class.http_status_codes[status_cd] : status_cd
131
+ data = get_exception_data
132
+ #We only send email if it has been configured in environment
133
+ send_email = should_email_on_exception?(exception, status_cd, verbose)
134
+ #We only send web hooks if they've been configured in environment
135
+ send_web_hooks = should_web_hook_on_exception?(exception, status_cd, verbose)
136
+ the_blamed = ExceptionNotifier.config[:git_repo_path].nil? ? nil : lay_blame(exception)
137
+
138
+ # Debugging output
139
+ verbose_output(exception, status_cd, file_path, send_email, send_web_hooks, request, the_blamed) if verbose
140
+
141
+ #TODO: is _rescue_action something from rails 3?
142
+ #if !(self.controller_name == 'application' && self.action_name == '_rescue_action')
143
+ perform_exception_notify_mailing(exception, data, request, the_blamed, verbose) if send_email
144
+ # Send Web Hook requests
145
+ HooksNotifier.deliver_exception_to_web_hooks(ExceptionNotifier.config, exception, self, request, data, the_blamed) if send_web_hooks
146
+
147
+ # We put the render call after the deliver call to ensure that, if the
148
+ # deliver raises an exception, we don't call render twice.
149
+ # Render the error page to the end user
150
+ render_error_template(file_path, status)
151
+ end
152
+
153
+ def is_local?
154
+ (consider_all_requests_local || local_request?)
155
+ end
156
+
157
+ def status_code_for_exception(exception)
158
+ self.class.error_class_status_codes[exception.class].nil? ?
159
+ '500' :
160
+ self.class.error_class_status_codes[exception.class].blank? ?
161
+ '200' :
162
+ self.class.error_class_status_codes[exception.class]
163
+ end
164
+
165
+ def render_error_template(file, status)
166
+ respond_to do |type|
167
+ type.html { render :file => file,
168
+ :layout => self.class.error_layout,
169
+ :status => status }
170
+ type.all { render :nothing => true,
171
+ :status => status}
172
+ end
173
+ end
174
+
175
+ end
@@ -0,0 +1,185 @@
1
+ require 'pathname'
2
+
3
+ class ExceptionNotifier < ActionMailer::Base
4
+
5
+ #andrewroth reported that @@config gets clobbered because rails loads this class twice when installed as a plugin, and adding the ||= fixed it.
6
+ @@config ||= {
7
+ # If left empty web hooks will not be engaged
8
+ :web_hooks => [],
9
+ :app_name => "[MYAPP]",
10
+ :version => "0.0.0",
11
+ :sender_address => "super.exception.notifier@example.com",
12
+ :exception_recipients => [],
13
+ # Customize the subject line
14
+ :subject_prepend => "[#{(defined?(Rails) ? Rails.env : RAILS_ENV).capitalize} ERROR] ",
15
+ :subject_append => nil,
16
+ # Include which sections of the exception email?
17
+ :sections => %w(request session environment backtrace),
18
+ :skip_local_notification => true,
19
+ :view_path => nil,
20
+ #Error Notification will be sent if the HTTP response code for the error matches one of the following error codes
21
+ :notify_error_codes => %W( 405 500 503 ),
22
+ #Error Notification will be sent if the error class matches one of the following error error classes
23
+ :notify_error_classes => %W( ),
24
+ :notify_other_errors => true,
25
+ :git_repo_path => nil,
26
+ :template_root => "#{File.dirname(__FILE__)}/../views"
27
+ }
28
+
29
+ cattr_accessor :config
30
+
31
+ def self.configure_exception_notifier(&block)
32
+ yield @@config
33
+ end
34
+
35
+ self.template_root = config[:template_root]
36
+
37
+ def self.reloadable?() false end
38
+
39
+ # Returns an array of potential filenames to look for
40
+ # eg. For the Exception Class - SuperExceptionNotifier::CustomExceptionClasses::MethodDisabled
41
+ # the filename handles are:
42
+ # super_exception_notifier_custom_exception_classes_method_disabled
43
+ # method_disabled
44
+ def self.exception_to_filenames(exception)
45
+ filenames = []
46
+ e = exception.to_s
47
+ filenames << ExceptionNotifier.filenamify(e)
48
+
49
+ last_colon = e.rindex(':')
50
+ unless last_colon.nil?
51
+ filenames << ExceptionNotifier.filenamify(e[(last_colon + 1)..(e.length - 1)])
52
+ end
53
+ filenames
54
+ end
55
+
56
+ # Converts Stringified Class Names to acceptable filename handles with underscores
57
+ def self.filenamify(str)
58
+ str.delete(':').gsub( /([A-Za-z])([A-Z])/, '\1' << '_' << '\2').downcase
59
+ end
60
+
61
+ # What is the path of the file we will render to the user based on a given status code?
62
+ def self.get_view_path_for_status_code(status_cd, verbose = false)
63
+ file_name = ExceptionNotifier.get_view_path(status_cd, verbose)
64
+ #ExceptionNotifierHelper::COMPAT_MODE ? "#{File.dirname(__FILE__)}/../rails/app/views/exception_notifiable/500.html" : "500.html"
65
+ file_name.nil? ? self.catch_all(verbose) : file_name
66
+ end
67
+
68
+ # def self.get_view_path_for_files(filenames = [])
69
+ # filepaths = filenames.map do |file|
70
+ # ExceptionNotifier.get_view_path(file)
71
+ # end.compact
72
+ # filepaths.empty? ? "#{File.dirname(__FILE__)}/../rails/app/views/exception_notifiable/500.html" : filepaths.first
73
+ # end
74
+
75
+ # What is the path of the file we will render to the user based on a given exception class?
76
+ def self.get_view_path_for_class(exception, verbose = false)
77
+ return self.catch_all if exception.nil?
78
+ return self.catch_all unless exception.is_a?(StandardError) || exception.is_a?(Class) # For some reason exception.is_a?(Class) works in console, but not when running in mongrel (ALWAYS returns false)?!?!?
79
+ filepaths = ExceptionNotifier.exception_to_filenames(exception).map do |file|
80
+ ExceptionNotifier.get_view_path(file, verbose)
81
+ end.compact
82
+ filepaths.empty? ? self.catch_all(verbose) : filepaths.first
83
+ end
84
+
85
+ def self.catch_all(verbose = false)
86
+ puts "[CATCH ALL INVOKED] #{File.dirname(__FILE__)}/../rails/app/views/exception_notifiable/500.html" if verbose
87
+ "#{File.dirname(__FILE__)}/../rails/app/views/exception_notifiable/500.html"
88
+ end
89
+
90
+ # Check the usual suspects
91
+ def self.get_view_path(file_name, verbose = false)
92
+ if File.exist?("#{RAILS_ROOT}/public/#{file_name}.html")
93
+ puts "[FOUND FILE] #{RAILS_ROOT}/public/#{file_name}.html" if verbose
94
+ "#{RAILS_ROOT}/public/#{file_name}.html"
95
+ elsif !config[:view_path].nil? && File.exist?("#{RAILS_ROOT}/#{config[:view_path]}/#{file_name}.html.erb")
96
+ puts "[FOUND FILE] #{RAILS_ROOT}/#{config[:view_path]}/#{file_name}.html.erb" if verbose
97
+ "#{RAILS_ROOT}/#{config[:view_path]}/#{file_name}.html.erb"
98
+ elsif !config[:view_path].nil? && File.exist?("#{RAILS_ROOT}/#{config[:view_path]}/#{file_name}.html")
99
+ puts "[FOUND FILE] #{RAILS_ROOT}/#{config[:view_path]}/#{file_name}.html" if verbose
100
+ "#{RAILS_ROOT}/#{config[:view_path]}/#{file_name}.html"
101
+ elsif File.exist?("#{File.dirname(__FILE__)}/../rails/app/views/exception_notifiable/#{file_name}.html.erb")
102
+ puts "[FOUND FILE] #{File.dirname(__FILE__)}/../rails/app/views/exception_notifiable/#{file_name}.html.erb" if verbose
103
+ "#{File.dirname(__FILE__)}/../rails/app/views/exception_notifiable/#{file_name}.html.erb"
104
+ elsif File.exist?("#{File.dirname(__FILE__)}/../rails/app/views/exception_notifiable/#{file_name}.html")
105
+ #ExceptionNotifierHelper::COMPAT_MODE ? "#{File.dirname(__FILE__)}/../rails/app/views/exception_notifiable/#{file_name}.html" : "#{status_cd}.html"
106
+ puts "[FOUND FILE] #{File.dirname(__FILE__)}/../rails/app/views/exception_notifiable/#{file_name}.html" if verbose
107
+ "#{File.dirname(__FILE__)}/../rails/app/views/exception_notifiable/#{file_name}.html"
108
+ else
109
+ nil
110
+ end
111
+ end
112
+
113
+ def exception_notification(exception, class_name = nil, method_name = nil, request = nil, data={}, the_blamed=nil)
114
+ body_hash = error_environment_data_hash(exception, class_name, method_name, request, data, the_blamed)
115
+ #Prefer to have custom, potentially HTML email templates available
116
+ #content_type "text/plain"
117
+ recipients config[:exception_recipients]
118
+ from config[:sender_address]
119
+
120
+ request.session.inspect unless request.nil? # Ensure session data is loaded (Rails 2.3 lazy-loading)
121
+
122
+ subject "#{config[:subject_prepend]}#{body_hash[:location]} (#{exception.class}) #{exception.message.inspect}#{config[:subject_append]}"
123
+ body body_hash
124
+ end
125
+
126
+ def background_exception_notification(exception, data = {}, the_blamed = nil)
127
+ exception_notification(exception, nil, nil, data, the_blamed)
128
+ end
129
+
130
+ def rake_exception_notification(exception, task, data={})
131
+ content_type "text/plain"
132
+
133
+ subject "#{email_prefix}#{task.name} (#{exception.class}) #{exception.message.inspect}"
134
+
135
+ recipients exception_recipients
136
+ from sender_address
137
+
138
+ body data.merge({ :task => task,
139
+ :exception => exception,
140
+ :backtrace => sanitize_backtrace(exception.backtrace),
141
+ :rails_root => rails_root,
142
+ :data => data,
143
+ :sections => sections.reject{|s| %w(request session).include?(s) }
144
+ })
145
+ end
146
+
147
+ private
148
+
149
+ def error_environment_data_hash(exception, class_name = nil, method_name = nil, request = nil, data={}, the_blamed=nil)
150
+ data.merge!({
151
+ :exception => exception,
152
+ :backtrace => sanitize_backtrace(exception.backtrace),
153
+ :rails_root => rails_root,
154
+ :data => data,
155
+ :the_blamed => the_blamed
156
+ })
157
+
158
+ data.merge!({:class_name => class_name}) if class_name
159
+ data.merge!({:method_name => method_name}) if method_name
160
+ if class_name && method_name
161
+ data.merge!({:location => "#{class_name}##{method_name}"})
162
+ else
163
+ data.merge!({:location => sanitize_backtrace([exception.backtrace.first]).first})
164
+ end
165
+ if request
166
+ data.merge!({:request => request})
167
+ data.merge!({:host => (request.env['HTTP_X_REAL_IP'] || request.env["HTTP_X_FORWARDED_HOST"] || request.env["HTTP_HOST"])})
168
+ data.merge!({:sections => config[:sections]})
169
+ else
170
+ # TODO: with refactoring in the view structure, the environment section could show useful ENV data even without a request?
171
+ data.merge!({:sections => config[:sections] - %w(request session environment)})
172
+ end
173
+ return data
174
+ end
175
+
176
+ def sanitize_backtrace(trace)
177
+ re = Regexp.new(/^#{Regexp.escape(rails_root)}/)
178
+ trace.map { |line| Pathname.new(line.gsub(re, "[RAILS_ROOT]")).cleanpath.to_s }
179
+ end
180
+
181
+ def rails_root
182
+ @rails_root ||= Pathname.new(RAILS_ROOT).cleanpath.to_s
183
+ end
184
+
185
+ end
@@ -0,0 +1,60 @@
1
+ require 'pp'
2
+
3
+ module ExceptionNotifierHelper
4
+ VIEW_PATH = "views/exception_notifier" unless defined?(VIEW_PATH)
5
+ APP_PATH = "#{RAILS_ROOT}/app/#{VIEW_PATH}" unless defined?(APP_PATH)
6
+ PARAM_FILTER_REPLACEMENT = "[FILTERED]" unless defined?(PARAM_FILTER_REPLACEMENT)
7
+ COMPAT_MODE = defined?(RAILS_GEM_VERSION) ? RAILS_GEM_VERSION < '2' : false unless defined?(COMPAT_MODE)
8
+
9
+ def render_section(section)
10
+ RAILS_DEFAULT_LOGGER.info("rendering section #{section.inspect}")
11
+ summary = render_overridable(section).strip
12
+ unless summary.blank?
13
+ title = render_overridable(:title, :locals => { :title => section }).strip
14
+ "#{title}\n\n#{summary.gsub(/^/, " ")}\n\n"
15
+ end
16
+ end
17
+
18
+ def render_overridable(partial, options={})
19
+ if File.exist?(path = "#{APP_PATH}/_#{partial}.html.erb") ||
20
+ File.exist?(path = "#{File.dirname(__FILE__)}/../#{VIEW_PATH}/_#{partial}.html.erb") ||
21
+ File.exist?(path = "#{APP_PATH}/_#{partial}.rhtml") ||
22
+ File.exist?(path = "#{APP_PATH}/_#{partial}.erb")
23
+ render(options.merge(:file => path, :use_full_path => false))
24
+ else
25
+ ""
26
+ end
27
+ end
28
+
29
+ def inspect_model_object(model, locals={})
30
+ render_overridable(:inspect_model,
31
+ :locals => { :inspect_model => model,
32
+ :show_instance_variables => true,
33
+ :show_attributes => true }.merge(locals))
34
+ end
35
+
36
+ def inspect_value(value)
37
+ len = 512
38
+ result = object_to_yaml(value).gsub(/\n/, "\n ").strip
39
+ result = result[0,len] + "... (#{result.length-len} bytes more)" if result.length > len+20
40
+ result
41
+ end
42
+
43
+ def object_to_yaml(object)
44
+ object.to_yaml.sub(/^---\s*/m, "")
45
+ end
46
+
47
+ def exclude_raw_post_parameters?
48
+ @controller && @controller.respond_to?(:filter_parameters)
49
+ end
50
+
51
+ def filter_sensitive_post_data_parameters(parameters)
52
+ exclude_raw_post_parameters? ? COMPAT_MODE ? @controller.filter_parameters(parameters) : @controller.__send__(:filter_parameters, parameters) : parameters
53
+ end
54
+
55
+ def filter_sensitive_post_data_from_env(env_key, env_value)
56
+ return env_value unless exclude_raw_post_parameters?
57
+ return PARAM_FILTER_REPLACEMENT if (env_key =~ /RAW_POST_DATA/i)
58
+ return COMPAT_MODE ? @controller.filter_parameters({env_key => env_value}).values[0] : @controller.__send__(:filter_parameters, {env_key => env_value}).values[0]
59
+ end
60
+ end
@@ -0,0 +1,91 @@
1
+ module Notifiable
2
+ include SuperExceptionNotifier::NotifiableHelper
3
+
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+
7
+ # Verbosity of the gem
8
+ base.cattr_accessor :notifiable_verbose
9
+ base.notifiable_verbose = false
10
+ # Do Not Ever send error notification emails for these Error Classes
11
+ base.cattr_accessor :notifiable_silent_exceptions
12
+ base.notifiable_silent_exceptions = SILENT_EXCEPTIONS
13
+ # Notification Level
14
+ base.cattr_accessor :notifiable_notification_level
15
+ base.notifiable_notification_level = [:email, :web_hooks]
16
+
17
+ # Since there is no concept of locality from a request here allow user to explicitly define which env's are noisy (send notifications)
18
+ base.cattr_accessor :notifiable_noisy_environments
19
+ base.notifiable_noisy_environments = [:production]
20
+ end
21
+
22
+ module ClassMethods
23
+ # set the exception_data deliverer OR retrieve the exception_data
24
+ def exception_data(deliverer = nil)
25
+ if deliverer
26
+ write_inheritable_attribute(:exception_data, deliverer)
27
+ else
28
+ read_inheritable_attribute(:exception_data)
29
+ end
30
+ end
31
+
32
+ def be_silent_for_exception?(exception)
33
+ self.notifiable_silent_exceptions.respond_to?(:any?) && self.notifiable_silent_exceptions.any? {|klass| klass === exception }
34
+ end
35
+
36
+ end
37
+
38
+ # Usage:
39
+ # notifiable { Klass.some_method }
40
+ # This will rescue any errors that occur within Klass.some_method
41
+ def notifiable(&block)
42
+ yield
43
+ rescue => exception
44
+ rescue_with_hooks(exception)
45
+ raise
46
+ end
47
+
48
+ def be_silent_for_exception?(exception)
49
+ self.class.be_silent_for_exception?(exception)
50
+ end
51
+
52
+ private
53
+
54
+ def notification_level_sends_email?
55
+ self.class.notifiable_notification_level.include?(:email)
56
+ end
57
+
58
+ def notification_level_sends_web_hooks?
59
+ self.class.notifiable_notification_level.include?(:web_hooks)
60
+ end
61
+
62
+ def rescue_with_hooks(exception)
63
+ verbose = self.class.notifiable_verbose
64
+ puts "[RESCUE STYLE] rescue_with_hooks" if verbose
65
+ data = get_exception_data
66
+ # With ExceptionNotifiable you have an inherent request, and using a status code makes sense.
67
+ # With Notifiable class to wrap around everything that doesn't have a request,
68
+ # the errors you want to be notified of need to be specified either positively or negatively
69
+ # 1. positive eg. set ExceptionNotifier.config[:notify_error_classes] to an array of classes
70
+ # set ExceptionNotifier.config[:notify_other_errors] to false
71
+ # 1. negative eg. set Klass.silent_exceptions to the ones to keep quiet
72
+ # set ExceptionNotifier.config[:notify_other_errors] to true
73
+ status_code = nil
74
+ #We only send email if it has been configured in environment
75
+ send_email = should_email_on_exception?(exception, status_code, verbose)
76
+ #We only send web hooks if they've been configured in environment
77
+ send_web_hooks = should_web_hook_on_exception?(exception, status_code, verbose)
78
+ the_blamed = ExceptionNotifier.config[:git_repo_path].nil? ? nil : lay_blame(exception)
79
+ verbose_output(exception, status_code, "rescued by handler", send_email, send_web_hooks, nil, the_blamed) if verbose
80
+ # Send the exception notificaiton email
81
+ perform_exception_notify_mailing(exception, data, nil, the_blamed, verbose) if send_email
82
+ # Send Web Hook requests
83
+ HooksNotifier.deliver_exception_to_web_hooks(ExceptionNotifier.config, exception, self, request, data, the_blamed) if send_web_hooks
84
+ end
85
+
86
+ def is_local? #like asking is_silent?
87
+ eenv = defined?(Rails) ? Rails.env : RAILS_ENV
88
+ !self.notifiable_noisy_environments.include?(eenv)
89
+ end
90
+
91
+ end