super_exception_notifier 3.0.2 → 3.0.4
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.
- data/VERSION.yml +1 -1
- data/lib/exception_notification/consider_local.rb +33 -0
- data/lib/exception_notification/custom_exception_classes.rb +14 -0
- data/lib/exception_notification/custom_exception_methods.rb +48 -0
- data/lib/exception_notification/deprecated_methods.rb +58 -0
- data/lib/exception_notification/exception_notifiable.rb +205 -0
- data/lib/exception_notification/git_blame.rb +50 -0
- data/lib/exception_notification/helpful_hashes.rb +64 -0
- data/lib/exception_notification/hooks_notifier.rb +53 -0
- data/lib/exception_notification/notifiable.rb +115 -0
- data/lib/exception_notification/notifiable_helper.rb +77 -0
- data/lib/exception_notification/notified_task.rb +15 -0
- data/lib/exception_notification/notifier.rb +174 -0
- data/lib/exception_notification/notifier_helper.rb +59 -0
- data/rails/init.rb +0 -3
- data/super_exception_notifier.gemspec +14 -1
- metadata +15 -2
data/VERSION.yml
CHANGED
@@ -0,0 +1,33 @@
|
|
1
|
+
#From rails/exception_notifier 2-3-stable branch - Not yet integrated into SuperExceptionNotifier...
|
2
|
+
|
3
|
+
#This didn't belong on ExceptionNotification::Notifier and made backtraces worse. To keep original functionality in place
|
4
|
+
#'ActionController::Base.send :include, ExceptionNotification::ConsiderLocal' or just include in your controller
|
5
|
+
module ExceptionNotification::ConsiderLocal
|
6
|
+
module ClassMethods
|
7
|
+
def self.included(target)
|
8
|
+
require 'ipaddr'
|
9
|
+
target.extend(ClassMethods)
|
10
|
+
end
|
11
|
+
|
12
|
+
def consider_local(*args)
|
13
|
+
local_addresses.concat(args.flatten.map { |a| IPAddr.new(a) })
|
14
|
+
end
|
15
|
+
|
16
|
+
def local_addresses
|
17
|
+
addresses = read_inheritable_attribute(:local_addresses)
|
18
|
+
unless addresses
|
19
|
+
addresses = [IPAddr.new("127.0.0.1")]
|
20
|
+
write_inheritable_attribute(:local_addresses, addresses)
|
21
|
+
end
|
22
|
+
addresses
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def local_request?
|
29
|
+
remote = IPAddr.new(request.remote_ip)
|
30
|
+
!self.class.local_addresses.detect { |addr| addr.include?(remote) }.nil?
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
#Copyright (c) 2008-2009 Peter H. Boling of 9thBit LLC
|
2
|
+
#Released under the MIT license
|
3
|
+
|
4
|
+
module ExceptionNotification::CustomExceptionClasses
|
5
|
+
|
6
|
+
class AccessDenied < StandardError; end
|
7
|
+
class ResourceGone < StandardError; end
|
8
|
+
class NotImplemented < StandardError; end
|
9
|
+
class PageNotFound < StandardError; end
|
10
|
+
class InvalidMethod < StandardError; end
|
11
|
+
class CorruptData < StandardError; end
|
12
|
+
class MethodDisabled < StandardError; end
|
13
|
+
|
14
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
#Copyright (c) 2008-2009 Peter H. Boling of 9thBit LLC
|
2
|
+
#Released under the MIT license
|
3
|
+
|
4
|
+
module ExceptionNotification::CustomExceptionMethods
|
5
|
+
|
6
|
+
protected
|
7
|
+
|
8
|
+
#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.
|
9
|
+
def resource_gone
|
10
|
+
raise ResourceGone
|
11
|
+
end
|
12
|
+
#Then for things that have never existed or have not for a long time we call not_implemented
|
13
|
+
def not_implemented
|
14
|
+
raise NotImplemented
|
15
|
+
end
|
16
|
+
#Resources that must be requested with a specific HTTP Method (GET, PUT, POST, DELETE, AJAX, etc) but are requested otherwise should:
|
17
|
+
def invalid_method
|
18
|
+
raise InvalidMethod
|
19
|
+
end
|
20
|
+
#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:
|
21
|
+
def corrupt_data
|
22
|
+
raise CorruptData
|
23
|
+
end
|
24
|
+
def page_not_found
|
25
|
+
raise PageNotFound
|
26
|
+
end
|
27
|
+
def record_not_found
|
28
|
+
raise ActiveRecord::RecordNotFound
|
29
|
+
end
|
30
|
+
def method_disabled
|
31
|
+
raise MethodDisabled
|
32
|
+
end
|
33
|
+
#The current user does not have enough privileges to access the requested resource
|
34
|
+
def access_denied
|
35
|
+
raise AccessDenied
|
36
|
+
end
|
37
|
+
|
38
|
+
def generic_error
|
39
|
+
error_stickie("Sorry, an error has occurred.")
|
40
|
+
corrupt_data
|
41
|
+
end
|
42
|
+
|
43
|
+
def invalid_page
|
44
|
+
error_stickie("Sorry, the page number you requested was not valid.")
|
45
|
+
page_not_found
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
#Copyright (c) 2008-2009 Peter H. Boling of 9thBit LLC
|
2
|
+
#Released under the MIT license
|
3
|
+
|
4
|
+
module ExceptionNotification::DeprecatedMethods
|
5
|
+
@@namespacing = "Better namespacing to allow for ExceptionNotifiable.rb and ExceptionNotifiable to have similar APIs"
|
6
|
+
@@rails2ruby = "An effort to make this a 'Ruby' Gem and not a strictly 'Rails' Gem"
|
7
|
+
|
8
|
+
def http_error_codes
|
9
|
+
deprecation_warning("http_error_codes", "error_class_status_codes")
|
10
|
+
error_class_status_codes
|
11
|
+
end
|
12
|
+
|
13
|
+
def http_error_codes=(arg)
|
14
|
+
deprecation_warning("http_error_codes", "error_class_status_codes")
|
15
|
+
error_class_status_codes=(arg)
|
16
|
+
end
|
17
|
+
|
18
|
+
def rails_error_codes
|
19
|
+
deprecation_warning("rails_error_codes", "error_class_status_codes", @@rails2ruby)
|
20
|
+
error_class_status_codes
|
21
|
+
end
|
22
|
+
|
23
|
+
def rails_error_codes=(arg)
|
24
|
+
deprecation_warning("rails_error_codes=", "error_class_status_codes=", @@rails2ruby)
|
25
|
+
error_class_status_codes=(arg)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Now defined in Object class by init.rb & ExceptionNotifiable.rb module,
|
29
|
+
# so we need to override them for with the controller settings
|
30
|
+
def exception_notifier_verbose
|
31
|
+
deprecation_warning("exception_notifier_verbose", "exception_notifiable_verbose", @@namespacing)
|
32
|
+
exception_notifiable_verbose
|
33
|
+
end
|
34
|
+
def silent_exceptions
|
35
|
+
deprecation_warning("silent_exceptions", "exception_notifiable_silent_exceptions", @@namespacing)
|
36
|
+
exception_notifiable_silent_exceptions
|
37
|
+
end
|
38
|
+
def notification_level
|
39
|
+
deprecation_warning("notification_level", "exception_notifiable_notification_level", @@namespacing)
|
40
|
+
exception_notifiable_notification_level
|
41
|
+
end
|
42
|
+
def exception_notifier_verbose=(arg)
|
43
|
+
deprecation_warning("exception_notifier_verbose=", "exception_notifiable_verbose=", @@namespacing)
|
44
|
+
exception_notifiable_verbose = arg
|
45
|
+
end
|
46
|
+
def silent_exceptions=(arg)
|
47
|
+
deprecation_warning("silent_exceptions=", "exception_notifiable_silent_exceptions=", @@namespacing)
|
48
|
+
exception_notifiable_silent_exceptions = arg
|
49
|
+
end
|
50
|
+
def notification_level=(arg)
|
51
|
+
deprecation_warning("notification_level=", "exception_notifiable_notification_level=", @@namespacing)
|
52
|
+
exception_notifiable_notification_level = arg
|
53
|
+
end
|
54
|
+
|
55
|
+
def deprecation_warning(old, new, reason = "")
|
56
|
+
puts "[DEPRECATION WARNING] ** Method '#{old}' has been replaced by '#{new}', please update your code.#{' Reason for change: ' + reason + '.' if reason}"
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,205 @@
|
|
1
|
+
require 'ipaddr'
|
2
|
+
|
3
|
+
module ExceptionNotification::ExceptionNotifiable
|
4
|
+
include ExceptionNotification::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
|
+
# Since there is no concept of locality from a request here allow user to explicitly define which env's are noisy (send notifications)
|
32
|
+
base.cattr_accessor :exception_notifiable_noisy_environments
|
33
|
+
base.exception_notifiable_noisy_environments = [:production]
|
34
|
+
|
35
|
+
base.cattr_accessor :exception_notifiable_pass_through
|
36
|
+
base.exception_notifiable_pass_through = false
|
37
|
+
end
|
38
|
+
|
39
|
+
module ClassMethods
|
40
|
+
include ExceptionNotification::DeprecatedMethods
|
41
|
+
|
42
|
+
# specifies ip addresses that should be handled as though local
|
43
|
+
def consider_local(*args)
|
44
|
+
local_addresses.concat(args.flatten.map { |a| IPAddr.new(a) })
|
45
|
+
end
|
46
|
+
|
47
|
+
def local_addresses
|
48
|
+
addresses = read_inheritable_attribute(:local_addresses)
|
49
|
+
unless addresses
|
50
|
+
addresses = [IPAddr.new("127.0.0.1")]
|
51
|
+
write_inheritable_attribute(:local_addresses, addresses)
|
52
|
+
end
|
53
|
+
addresses
|
54
|
+
end
|
55
|
+
|
56
|
+
# set the exception_data deliverer OR retrieve the exception_data
|
57
|
+
def exception_data(deliverer = nil)
|
58
|
+
if deliverer
|
59
|
+
write_inheritable_attribute(:exception_data, deliverer)
|
60
|
+
else
|
61
|
+
read_inheritable_attribute(:exception_data)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def be_silent_for_exception?(exception)
|
66
|
+
self.exception_notifiable_silent_exceptions.respond_to?(:any?) && self.exception_notifiable_silent_exceptions.any? {|klass| klass === exception }
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
def be_silent_for_exception?(exception)
|
72
|
+
self.class.be_silent_for_exception?(exception)
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
private
|
77
|
+
|
78
|
+
def environment_is_noisy?
|
79
|
+
self.class.exception_notifiable_noisy_environments.include?(Rails.env)
|
80
|
+
end
|
81
|
+
|
82
|
+
def notification_level_sends_email?
|
83
|
+
self.class.exception_notifiable_notification_level.include?(:email)
|
84
|
+
end
|
85
|
+
|
86
|
+
def notification_level_sends_web_hooks?
|
87
|
+
self.class.exception_notifiable_notification_level.include?(:web_hooks)
|
88
|
+
end
|
89
|
+
|
90
|
+
def notification_level_renders?
|
91
|
+
self.class.exception_notifiable_notification_level.include?(:render)
|
92
|
+
end
|
93
|
+
|
94
|
+
# overrides Rails' local_request? method to also check any ip
|
95
|
+
# addresses specified through consider_local.
|
96
|
+
def local_request?
|
97
|
+
remote = IPAddr.new(request.remote_ip)
|
98
|
+
!self.class.local_addresses.detect { |addr| addr.include?(remote) }.nil?
|
99
|
+
end
|
100
|
+
|
101
|
+
# When the action being executed has its own local error handling (rescue)
|
102
|
+
# Or when the error accurs somewhere without a subsequent render (eg. method calls in console)
|
103
|
+
def rescue_with_handler(exception)
|
104
|
+
to_return = super
|
105
|
+
if to_return
|
106
|
+
verbose = self.class.exception_notifiable_verbose
|
107
|
+
puts "[RESCUE STYLE] rescue_with_handler" if verbose
|
108
|
+
data = get_exception_data
|
109
|
+
status_code = status_code_for_exception(exception)
|
110
|
+
#We only send email if it has been configured in environment
|
111
|
+
send_email = should_email_on_exception?(exception, status_code, verbose)
|
112
|
+
#We only send web hooks if they've been configured in environment
|
113
|
+
send_web_hooks = should_web_hook_on_exception?(exception, status_code, verbose)
|
114
|
+
the_blamed = ExceptionNotification::Notifier.config[:git_repo_path].nil? ? nil : lay_blame(exception)
|
115
|
+
rejected_sections = %w(request session)
|
116
|
+
# Debugging output
|
117
|
+
verbose_output(exception, status_code, "rescued by handler", send_email, send_web_hooks, nil, the_blamed, rejected_sections) if verbose
|
118
|
+
# Send the exception notification email
|
119
|
+
perform_exception_notify_mailing(exception, data, nil, the_blamed, verbose, rejected_sections) if send_email
|
120
|
+
# Send Web Hook requests
|
121
|
+
ExceptionNotification::HooksNotifier.deliver_exception_to_web_hooks(ExceptionNotification::Notifier.config, exception, self, request, data, the_blamed) if send_web_hooks
|
122
|
+
end
|
123
|
+
pass_it_on(exception)
|
124
|
+
to_return
|
125
|
+
end
|
126
|
+
|
127
|
+
# When the action being executed is letting SEN handle the exception completely
|
128
|
+
def rescue_action_in_public(exception)
|
129
|
+
# If the error class is NOT listed in the rails_errror_class hash then we get a generic 500 error:
|
130
|
+
# 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
|
131
|
+
# OTW the error class is listed!
|
132
|
+
verbose = self.class.exception_notifiable_verbose
|
133
|
+
puts "[RESCUE STYLE] rescue_action_in_public" if verbose
|
134
|
+
status_code = status_code_for_exception(exception)
|
135
|
+
if status_code == '200'
|
136
|
+
notify_and_render_error_template(status_code, request, exception, ExceptionNotification::Notifier.get_view_path_for_class(exception, verbose), verbose)
|
137
|
+
else
|
138
|
+
notify_and_render_error_template(status_code, request, exception, ExceptionNotification::Notifier.get_view_path_for_status_code(status_code, verbose), verbose)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def notify_and_render_error_template(status_cd, request, exception, file_path, verbose = false)
|
143
|
+
status = self.class.http_status_codes[status_cd] ? status_cd + " " + self.class.http_status_codes[status_cd] : status_cd
|
144
|
+
data = get_exception_data
|
145
|
+
#We only send email if it has been configured in environment
|
146
|
+
send_email = should_email_on_exception?(exception, status_cd, verbose)
|
147
|
+
#We only send web hooks if they've been configured in environment
|
148
|
+
send_web_hooks = should_web_hook_on_exception?(exception, status_cd, verbose)
|
149
|
+
the_blamed = ExceptionNotification::Notifier.config[:git_repo_path].nil? ? nil : lay_blame(exception)
|
150
|
+
rejected_sections = request.nil? ? %w(request session) : []
|
151
|
+
# Debugging output
|
152
|
+
verbose_output(exception, status_cd, file_path, send_email, send_web_hooks, request, the_blamed, rejected_sections) if verbose
|
153
|
+
#TODO: is _rescue_action something from rails 3?
|
154
|
+
#if !(self.controller_name == 'application' && self.action_name == '_rescue_action')
|
155
|
+
# Send the exception notification email
|
156
|
+
perform_exception_notify_mailing(exception, data, request, the_blamed, verbose, rejected_sections) if send_email
|
157
|
+
# Send Web Hook requests
|
158
|
+
ExceptionNotification::HooksNotifier.deliver_exception_to_web_hooks(ExceptionNotification::Notifier.config, exception, self, request, data, the_blamed) if send_web_hooks
|
159
|
+
|
160
|
+
# We put the render call after the deliver call to ensure that, if the
|
161
|
+
# deliver raises an exception, we don't call render twice.
|
162
|
+
# Render the error page to the end user
|
163
|
+
render_error_template(file_path, status)
|
164
|
+
pass_it_on(exception, request)
|
165
|
+
end
|
166
|
+
|
167
|
+
def pass_it_on(exception, request = nil)
|
168
|
+
begin
|
169
|
+
request ||= {:params => {}}
|
170
|
+
case self.class.exception_notifiable_pass_through
|
171
|
+
when :hoptoad then
|
172
|
+
HoptoadNotifier.notify(exception, {:request => request})
|
173
|
+
puts "[PASS-IT-ON] HOPTOAD NOTIFIED" if verbose
|
174
|
+
else
|
175
|
+
puts "[PASS-IT-ON] NO" if verbose
|
176
|
+
#nothing
|
177
|
+
end
|
178
|
+
rescue
|
179
|
+
#nothing
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def is_local?
|
184
|
+
(consider_all_requests_local || local_request?)
|
185
|
+
end
|
186
|
+
|
187
|
+
def status_code_for_exception(exception)
|
188
|
+
self.class.error_class_status_codes[exception.class].nil? ?
|
189
|
+
'500' :
|
190
|
+
self.class.error_class_status_codes[exception.class].blank? ?
|
191
|
+
'200' :
|
192
|
+
self.class.error_class_status_codes[exception.class]
|
193
|
+
end
|
194
|
+
|
195
|
+
def render_error_template(file, status)
|
196
|
+
respond_to do |type|
|
197
|
+
type.html { render :file => file,
|
198
|
+
:layout => self.class.error_layout,
|
199
|
+
:status => status }
|
200
|
+
type.all { render :nothing => true,
|
201
|
+
:status => status}
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
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 ExceptionNotification::GitBlame
|
5
|
+
|
6
|
+
def lay_blame(exception)
|
7
|
+
error = {}
|
8
|
+
unless(ExceptionNotification::Notifier.config[:git_repo_path].nil?)
|
9
|
+
if(exception.class == ActionView::TemplateError)
|
10
|
+
blame = blame_output(exception.line_number, "app/views/#{exception.file_name}")
|
11
|
+
error[:author] = blame[/^author\s.+$/].gsub(/author\s/,'')
|
12
|
+
error[:line] = exception.line_number
|
13
|
+
error[:file] = exception.file_name
|
14
|
+
else
|
15
|
+
exception.backtrace.each do |line|
|
16
|
+
file = exception_in_project?(line[/^.+?(?=:)/])
|
17
|
+
unless(file.nil?)
|
18
|
+
line_number = line[/:\d+:/].gsub(/[^\d]/,'')
|
19
|
+
# Use relative path or weird stuff happens
|
20
|
+
blame = blame_output(line_number, file.gsub(Regexp.new("#{RAILS_ROOT}/"),''))
|
21
|
+
error[:author] = blame[/^author\s.+$/].sub(/author\s/,'')
|
22
|
+
error[:line] = line_number
|
23
|
+
error[:file] = file
|
24
|
+
break
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
error
|
30
|
+
end
|
31
|
+
|
32
|
+
def blame_output(line_number, path)
|
33
|
+
app_directory = Dir.pwd
|
34
|
+
Dir.chdir ExceptionNotification::Notifier.config[:git_repo_path]
|
35
|
+
blame = `git blame -p -L #{line_number},#{line_number} #{path}`
|
36
|
+
Dir.chdir app_directory
|
37
|
+
|
38
|
+
blame
|
39
|
+
end
|
40
|
+
|
41
|
+
def exception_in_project?(path) # should be a path like /path/to/broken/thingy.rb
|
42
|
+
dir = File.split(path).first rescue ''
|
43
|
+
if(File.directory?(dir) and !(path =~ /vendor\/plugins/) and !(path =~ /vendor\/gems/) and path.include?(RAILS_ROOT))
|
44
|
+
path
|
45
|
+
else
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
#Copyright (c) 2008-2009 Peter H. Boling of 9thBit LLC
|
2
|
+
#Released under the MIT license
|
3
|
+
|
4
|
+
module ExceptionNotification::HelpfulHashes
|
5
|
+
unless defined?(SILENT_EXCEPTIONS)
|
6
|
+
noiseless = []
|
7
|
+
noiseless << ActiveRecord::RecordNotFound if defined?(ActiveRecord)
|
8
|
+
if defined?(ActionController)
|
9
|
+
noiseless << ActionController::UnknownController
|
10
|
+
noiseless << ActionController::UnknownAction
|
11
|
+
noiseless << ActionController::RoutingError
|
12
|
+
noiseless << ActionController::MethodNotAllowed
|
13
|
+
end
|
14
|
+
SILENT_EXCEPTIONS = noiseless
|
15
|
+
end
|
16
|
+
|
17
|
+
# TODO: use ActionController::StatusCodes
|
18
|
+
HTTP_STATUS_CODES = {
|
19
|
+
"400" => "Bad Request",
|
20
|
+
"403" => "Forbidden",
|
21
|
+
"404" => "Not Found",
|
22
|
+
"405" => "Method Not Allowed",
|
23
|
+
"410" => "Gone",
|
24
|
+
"418" => "I'm a teapot",
|
25
|
+
"422" => "Unprocessable Entity",
|
26
|
+
"423" => "Locked",
|
27
|
+
"500" => "Internal Server Error",
|
28
|
+
"501" => "Not Implemented",
|
29
|
+
"503" => "Service Unavailable"
|
30
|
+
} unless defined?(HTTP_STATUS_CODES)
|
31
|
+
|
32
|
+
def codes_for_error_classes
|
33
|
+
#TODO: Format whitespace
|
34
|
+
classes = {
|
35
|
+
# These are standard errors in rails / ruby
|
36
|
+
NameError => "503",
|
37
|
+
TypeError => "503",
|
38
|
+
RuntimeError => "500",
|
39
|
+
ArgumentError => "500",
|
40
|
+
# These are custom error names defined in lib/super_exception_notifier/custom_exception_classes
|
41
|
+
AccessDenied => "403",
|
42
|
+
PageNotFound => "404",
|
43
|
+
InvalidMethod => "405",
|
44
|
+
ResourceGone => "410",
|
45
|
+
CorruptData => "422",
|
46
|
+
NoMethodError => "500",
|
47
|
+
NotImplemented => "501",
|
48
|
+
MethodDisabled => "200"
|
49
|
+
}
|
50
|
+
# Highly dependent on the verison of rails, so we're very protective about these'
|
51
|
+
classes.merge!({ ActionView::TemplateError => "500"}) if defined?(ActionView) && ActionView.const_defined?(:TemplateError)
|
52
|
+
classes.merge!({ ActiveRecord::RecordNotFound => "400" }) if defined?(ActiveRecord) && ActiveRecord.const_defined?(:RecordNotFound)
|
53
|
+
classes.merge!({ ActiveResource::ResourceNotFound => "404" }) if defined?(ActiveResource) && ActiveResource.const_defined?(:ResourceNotFound)
|
54
|
+
|
55
|
+
if defined?(ActionController)
|
56
|
+
classes.merge!({ ActionController::UnknownController => "404" }) if ActionController.const_defined?(:UnknownController)
|
57
|
+
classes.merge!({ ActionController::MissingTemplate => "404" }) if ActionController.const_defined?(:MissingTemplate)
|
58
|
+
classes.merge!({ ActionController::MethodNotAllowed => "405" }) if ActionController.const_defined?(:MethodNotAllowed)
|
59
|
+
classes.merge!({ ActionController::UnknownAction => "501" }) if ActionController.const_defined?(:UnknownAction)
|
60
|
+
classes.merge!({ ActionController::RoutingError => "404" }) if ActionController.const_defined?(:RoutingError)
|
61
|
+
classes.merge!({ ActionController::InvalidAuthenticityToken => "405" }) if ActionController.const_defined?(:InvalidAuthenticityToken)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
module ExceptionNotification::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
|
@@ -0,0 +1,115 @@
|
|
1
|
+
module ExceptionNotification::Notifiable
|
2
|
+
include ExceptionNotification::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
|
+
|
21
|
+
base.cattr_accessor :notifiable_pass_through
|
22
|
+
base.notifiable_pass_through = false
|
23
|
+
end
|
24
|
+
|
25
|
+
module ClassMethods
|
26
|
+
# set the exception_data deliverer OR retrieve the exception_data
|
27
|
+
def exception_data(deliverer = nil)
|
28
|
+
if deliverer
|
29
|
+
write_inheritable_attribute(:exception_data, deliverer)
|
30
|
+
else
|
31
|
+
read_inheritable_attribute(:exception_data)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def be_silent_for_exception?(exception)
|
36
|
+
self.notifiable_silent_exceptions.respond_to?(:any?) && self.notifiable_silent_exceptions.any? {|klass| klass === exception }
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
# Usage:
|
42
|
+
# notifiable { Klass.some_method }
|
43
|
+
# This will rescue any errors that occur within Klass.some_method
|
44
|
+
def notifiable(&block)
|
45
|
+
yield
|
46
|
+
rescue => exception
|
47
|
+
rescue_with_hooks(exception)
|
48
|
+
raise
|
49
|
+
end
|
50
|
+
|
51
|
+
def be_silent_for_exception?(exception)
|
52
|
+
self.class.be_silent_for_exception?(exception)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def environment_is_noisy?
|
58
|
+
!self.notifiable_noisy_environments.include?(Rails.env)
|
59
|
+
end
|
60
|
+
|
61
|
+
def notification_level_sends_email?
|
62
|
+
self.class.notifiable_notification_level.include?(:email)
|
63
|
+
end
|
64
|
+
|
65
|
+
def notification_level_sends_web_hooks?
|
66
|
+
self.class.notifiable_notification_level.include?(:web_hooks)
|
67
|
+
end
|
68
|
+
|
69
|
+
def rescue_with_hooks(exception)
|
70
|
+
verbose = self.class.notifiable_verbose
|
71
|
+
puts "[RESCUE STYLE] rescue_with_hooks" if verbose
|
72
|
+
data = get_exception_data
|
73
|
+
# With ExceptionNotifiable you have an inherent request, and using a status code makes sense.
|
74
|
+
# With Notifiable class to wrap around everything that doesn't have a request,
|
75
|
+
# the errors you want to be notified of need to be specified either positively or negatively
|
76
|
+
# 1. positive eg. set ExceptionNotifier.config[:notify_error_classes] to an array of classes
|
77
|
+
# set ExceptionNotifier.config[:notify_other_errors] to false
|
78
|
+
# 1. negative eg. set Klass.silent_exceptions to the ones to keep quiet
|
79
|
+
# set ExceptionNotifier.config[:notify_other_errors] to true
|
80
|
+
status_code = nil
|
81
|
+
#We only send email if it has been configured in environment
|
82
|
+
send_email = should_email_on_exception?(exception, status_code, verbose)
|
83
|
+
#We only send web hooks if they've been configured in environment
|
84
|
+
send_web_hooks = should_web_hook_on_exception?(exception, status_code, verbose)
|
85
|
+
the_blamed = ExceptionNotification::Notifier.config[:git_repo_path].nil? ? nil : lay_blame(exception)
|
86
|
+
rejected_sections = %w(request session)
|
87
|
+
verbose_output(exception, status_code, "rescued by handler", send_email, send_web_hooks, nil, the_blamed, rejected_sections) if verbose
|
88
|
+
# Send the exception notification email
|
89
|
+
perform_exception_notify_mailing(exception, data, nil, the_blamed, verbose, rejected_sections) if send_email
|
90
|
+
# Send Web Hook requests
|
91
|
+
ExceptionNotification::HooksNotifier.deliver_exception_to_web_hooks(ExceptionNotification::Notifier.config, exception, self, request, data, the_blamed) if send_web_hooks
|
92
|
+
pass_it_on(exception)
|
93
|
+
end
|
94
|
+
|
95
|
+
def pass_it_on(exception, request = nil)
|
96
|
+
begin
|
97
|
+
request ||= {:params => {}}
|
98
|
+
case self.class.notifiable_pass_through
|
99
|
+
when :hoptoad then
|
100
|
+
HoptoadNotifier.notify(exception, {:request => request})
|
101
|
+
puts "[PASS-IT-ON] HOPTOAD NOTIFIED" if verbose
|
102
|
+
else
|
103
|
+
puts "[PASS-IT-ON] NO" if verbose
|
104
|
+
#nothing
|
105
|
+
end
|
106
|
+
rescue
|
107
|
+
#nothing
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def is_local? #like asking is_silent?
|
112
|
+
self.notifiable_noisy_environments.include?(Rails.env)
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
#Copyright (c) 2008-2009 Peter H. Boling of 9thBit LLC
|
2
|
+
#Released under the MIT license
|
3
|
+
|
4
|
+
module ExceptionNotification::NotifiableHelper
|
5
|
+
include ExceptionNotification::CustomExceptionClasses
|
6
|
+
include ExceptionNotification::CustomExceptionMethods
|
7
|
+
include ExceptionNotification::HelpfulHashes
|
8
|
+
include ExceptionNotification::GitBlame
|
9
|
+
include ExceptionNotification::HooksNotifier
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def get_method_name
|
14
|
+
if /`(.*)'/.match(caller.first)
|
15
|
+
return $1
|
16
|
+
end
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def get_exception_data
|
21
|
+
deliverer = self.class.exception_data
|
22
|
+
return case deliverer
|
23
|
+
when nil then {}
|
24
|
+
when Symbol then send(deliverer)
|
25
|
+
when Proc then deliverer.call(self)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def verbose_output(exception, status_cd, file_path, send_email, send_web_hooks, request = nil, the_blamed = nil, rejected_sections = nil)
|
30
|
+
puts "[EXCEPTION] #{exception}"
|
31
|
+
puts "[EXCEPTION CLASS] #{exception.class}"
|
32
|
+
puts "[EXCEPTION STATUS_CD] #{status_cd}"
|
33
|
+
puts "[ERROR LAYOUT] #{self.class.error_layout}" if self.class.respond_to?(:error_layout)
|
34
|
+
puts "[ERROR VIEW PATH] #{ExceptionNotification::Notifier.config[:view_path]}" if !ExceptionNotification::Notifier.nil? && !ExceptionNotification::Notifier.config[:view_path].nil?
|
35
|
+
puts "[ERROR FILE PATH] #{file_path.inspect}"
|
36
|
+
puts "[ERROR EMAIL] #{send_email ? "YES" : "NO"}"
|
37
|
+
puts "[ERROR WEB HOOKS] #{send_web_hooks ? "YES" : "NO"}"
|
38
|
+
puts "[THE BLAMED] #{the_blamed}"
|
39
|
+
puts "[SECTIONS] #{ExceptionNotification::Notifier.sections_for_email(rejected_sections, request)}"
|
40
|
+
req = request ? " for request_uri=#{request.request_uri} and env=#{request.env.inspect}" : ""
|
41
|
+
logger.error("render_error(#{status_cd}, #{self.class.http_status_codes[status_cd]}) invoked#{req}") if self.class.respond_to?(:http_status_codes) && !logger.nil?
|
42
|
+
end
|
43
|
+
|
44
|
+
def perform_exception_notify_mailing(exception, data, request = nil, the_blamed = nil, verbose = false, rejected_sections = nil)
|
45
|
+
if ExceptionNotification::Notifier.config[:exception_recipients].blank?
|
46
|
+
puts "[EMAIL NOTIFICATION] ExceptionNotification::Notifier.config[:exception_recipients] is blank, notification cancelled!" if verbose
|
47
|
+
else
|
48
|
+
class_name = self.respond_to?(:controller_name) ? self.controller_name : self.to_s
|
49
|
+
method_name = self.respond_to?(:action_name) ? self.action_name : get_method_name
|
50
|
+
ExceptionNotification::Notifier.deliver_exception_notification(exception, class_name, method_name,
|
51
|
+
request, data, the_blamed, rejected_sections)
|
52
|
+
puts "[EMAIL NOTIFICATION] Sent" if verbose
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def should_email_on_exception?(exception, status_cd = nil, verbose = false)
|
57
|
+
environment_is_noisy? && notification_level_sends_email? && !ExceptionNotification::Notifier.config[:exception_recipients].blank? && should_notify_on_exception?(exception, status_cd, verbose)
|
58
|
+
end
|
59
|
+
|
60
|
+
def should_web_hook_on_exception?(exception, status_cd = nil, verbose = false)
|
61
|
+
environment_is_noisy? && notification_level_sends_web_hooks? && !ExceptionNotification::Notifier.config[:web_hooks].blank? && should_notify_on_exception?(exception, status_cd, verbose)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Relies on the base class to define be_silent_for_exception?
|
65
|
+
def should_notify_on_exception?(exception, status_cd = nil, verbose = false)
|
66
|
+
# don't notify (email or web hooks) on exceptions raised locally
|
67
|
+
verbose && ExceptionNotification::Notifier.config[:skip_local_notification] && is_local? ?
|
68
|
+
"[NOTIFY LOCALLY] NO" :
|
69
|
+
nil
|
70
|
+
return false if ExceptionNotification::Notifier.config[:skip_local_notification] && is_local?
|
71
|
+
# don't notify (email or web hooks) exceptions raised that match ExceptionNotifiable.notifiable_silent_exceptions
|
72
|
+
return false if self.be_silent_for_exception?(exception)
|
73
|
+
return true if ExceptionNotification::Notifier.config[:notify_error_classes].include?(exception.class)
|
74
|
+
return true if !status_cd.nil? && ExceptionNotification::Notifier.config[:notify_error_codes].include?(status_cd)
|
75
|
+
return ExceptionNotification::Notifier.config[:notify_other_errors]
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
3
|
+
class ExceptionNotification::Notifier < 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 << ExceptionNotification::Notifier.filenamify(e)
|
48
|
+
|
49
|
+
last_colon = e.rindex(':')
|
50
|
+
unless last_colon.nil?
|
51
|
+
filenames << ExceptionNotification::Notifier.filenamify(e[(last_colon + 1)..(e.length - 1)])
|
52
|
+
end
|
53
|
+
filenames
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.sections_for_email(rejected_sections, request)
|
57
|
+
rejected_sections = rejected_sections.nil? ? request.nil? ? %w(request session) : [] : rejected_sections
|
58
|
+
rejected_sections.empty? ? config[:sections] : config[:sections].reject{|s| rejected_sections.include?(s) }
|
59
|
+
end
|
60
|
+
|
61
|
+
# Converts Stringified Class Names to acceptable filename handles with underscores
|
62
|
+
def self.filenamify(str)
|
63
|
+
str.delete(':').gsub( /([A-Za-z])([A-Z])/, '\1' << '_' << '\2').downcase
|
64
|
+
end
|
65
|
+
|
66
|
+
# What is the path of the file we will render to the user based on a given status code?
|
67
|
+
def self.get_view_path_for_status_code(status_cd, verbose = false)
|
68
|
+
file_name = ExceptionNotification::Notifier.get_view_path(status_cd, verbose)
|
69
|
+
file_name.nil? ? self.catch_all(verbose) : file_name
|
70
|
+
end
|
71
|
+
|
72
|
+
# def self.get_view_path_for_files(filenames = [])
|
73
|
+
# filepaths = filenames.map do |file|
|
74
|
+
# ExceptionNotification::Notifier.get_view_path(file)
|
75
|
+
# end.compact
|
76
|
+
# filepaths.empty? ? "#{File.dirname(__FILE__)}/../rails/app/views/exception_notifiable/500.html" : filepaths.first
|
77
|
+
# end
|
78
|
+
|
79
|
+
# What is the path of the file we will render to the user based on a given exception class?
|
80
|
+
def self.get_view_path_for_class(exception, verbose = false)
|
81
|
+
return self.catch_all(verbose) if exception.nil?
|
82
|
+
#return self.catch_all(verbose) 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)?!?!?
|
83
|
+
filepaths = ExceptionNotification::Notifier.exception_to_filenames(exception).map do |file|
|
84
|
+
ExceptionNotification::Notifier.get_view_path(file, verbose)
|
85
|
+
end.compact
|
86
|
+
filepaths.empty? ? self.catch_all(verbose) : filepaths.first
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.catch_all(verbose = false)
|
90
|
+
puts "[CATCH ALL INVOKED] #{File.dirname(__FILE__)}/../../rails/app/views/exception_notifiable/500.html" if verbose
|
91
|
+
"#{File.dirname(__FILE__)}/../../rails/app/views/exception_notifiable/500.html"
|
92
|
+
end
|
93
|
+
|
94
|
+
# Check the usual suspects
|
95
|
+
def self.get_view_path(file_name, verbose = false)
|
96
|
+
if File.exist?("#{RAILS_ROOT}/public/#{file_name}.html")
|
97
|
+
puts "[FOUND FILE:A] #{RAILS_ROOT}/public/#{file_name}.html" if verbose
|
98
|
+
"#{RAILS_ROOT}/public/#{file_name}.html"
|
99
|
+
elsif !config[:view_path].nil? && File.exist?("#{RAILS_ROOT}/#{config[:view_path]}/#{file_name}.html.erb")
|
100
|
+
puts "[FOUND FILE:B] #{RAILS_ROOT}/#{config[:view_path]}/#{file_name}.html.erb" if verbose
|
101
|
+
"#{RAILS_ROOT}/#{config[:view_path]}/#{file_name}.html.erb"
|
102
|
+
elsif !config[:view_path].nil? && File.exist?("#{RAILS_ROOT}/#{config[:view_path]}/#{file_name}.html")
|
103
|
+
puts "[FOUND FILE:C] #{RAILS_ROOT}/#{config[:view_path]}/#{file_name}.html" if verbose
|
104
|
+
"#{RAILS_ROOT}/#{config[:view_path]}/#{file_name}.html"
|
105
|
+
elsif File.exist?("#{File.dirname(__FILE__)}/../../rails/app/views/exception_notifiable/#{file_name}.html.erb")
|
106
|
+
puts "[FOUND FILE:D] #{File.dirname(__FILE__)}/../../rails/app/views/exception_notifiable/#{file_name}.html.erb" if verbose
|
107
|
+
"#{File.dirname(__FILE__)}/../../rails/app/views/exception_notifiable/#{file_name}.html.erb"
|
108
|
+
elsif File.exist?("#{File.dirname(__FILE__)}/../../rails/app/views/exception_notifiable/#{file_name}.html")
|
109
|
+
puts "[FOUND FILE:E] #{File.dirname(__FILE__)}/../../rails/app/views/exception_notifiable/#{file_name}.html" if verbose
|
110
|
+
"#{File.dirname(__FILE__)}/../../rails/app/views/exception_notifiable/#{file_name}.html"
|
111
|
+
else
|
112
|
+
nil
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def exception_notification(exception, class_name = nil, method_name = nil, request = nil, data = {}, the_blamed = nil, rejected_sections = nil)
|
117
|
+
body_hash = error_environment_data_hash(exception, class_name, method_name, request, data, the_blamed, rejected_sections)
|
118
|
+
#Prefer to have custom, potentially HTML email templates available
|
119
|
+
#content_type "text/plain"
|
120
|
+
recipients config[:exception_recipients]
|
121
|
+
from config[:sender_address]
|
122
|
+
|
123
|
+
request.session.inspect unless request.nil? # Ensure session data is loaded (Rails 2.3 lazy-loading)
|
124
|
+
|
125
|
+
subject "#{config[:subject_prepend]}#{body_hash[:location]} (#{exception.class}) #{exception.message.inspect}#{config[:subject_append]}"
|
126
|
+
body body_hash
|
127
|
+
end
|
128
|
+
|
129
|
+
def background_exception_notification(exception, data = {}, the_blamed = nil, rejected_sections = %w(request session))
|
130
|
+
exception_notification(exception, nil, nil, nil, data, the_blamed, rejected_sections)
|
131
|
+
end
|
132
|
+
|
133
|
+
def rake_exception_notification(exception, task, data={}, the_blamed = nil, rejected_sections = %w(request session))
|
134
|
+
exception_notification(exception, "", "#{task.name}", nil, data, the_blamed, rejected_sections)
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
def error_environment_data_hash(exception, class_name = nil, method_name = nil, request = nil, data = {}, the_blamed = nil, rejected_sections = nil)
|
140
|
+
data.merge!({
|
141
|
+
:exception => exception,
|
142
|
+
:backtrace => sanitize_backtrace(exception.backtrace),
|
143
|
+
:rails_root => rails_root,
|
144
|
+
:data => data,
|
145
|
+
:the_blamed => the_blamed
|
146
|
+
})
|
147
|
+
|
148
|
+
data.merge!({:class_name => class_name}) if class_name
|
149
|
+
data.merge!({:method_name => method_name}) if method_name
|
150
|
+
if class_name && method_name
|
151
|
+
data.merge!({:location => "#{class_name}##{method_name}"})
|
152
|
+
elsif method_name
|
153
|
+
data.merge!({:location => "#{method_name}"})
|
154
|
+
else
|
155
|
+
data.merge!({:location => sanitize_backtrace([exception.backtrace.first]).first})
|
156
|
+
end
|
157
|
+
if request
|
158
|
+
data.merge!({:request => request})
|
159
|
+
data.merge!({:host => (request.env['HTTP_X_REAL_IP'] || request.env["HTTP_X_FORWARDED_HOST"] || request.env["HTTP_HOST"])})
|
160
|
+
end
|
161
|
+
data.merge!({:sections => ExceptionNotification::Notifier.sections_for_email(rejected_sections, request)})
|
162
|
+
return data
|
163
|
+
end
|
164
|
+
|
165
|
+
def sanitize_backtrace(trace)
|
166
|
+
re = Regexp.new(/^#{Regexp.escape(rails_root)}/)
|
167
|
+
trace.map { |line| Pathname.new(line.gsub(re, "[RAILS_ROOT]")).cleanpath.to_s }
|
168
|
+
end
|
169
|
+
|
170
|
+
def rails_root
|
171
|
+
@rails_root ||= Pathname.new(RAILS_ROOT).cleanpath.to_s
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'pp'
|
2
|
+
|
3
|
+
module ExceptionNotification::NotifierHelper
|
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
|
+
|
8
|
+
def render_section(section)
|
9
|
+
RAILS_DEFAULT_LOGGER.info("rendering section #{section.inspect}")
|
10
|
+
summary = render_overridable(section).strip
|
11
|
+
unless summary.blank?
|
12
|
+
title = render_overridable(:title, :locals => { :title => section }).strip
|
13
|
+
"#{title}\n\n#{summary.gsub(/^/, " ")}\n\n"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def render_overridable(partial, options={})
|
18
|
+
if File.exist?(path = "#{APP_PATH}/_#{partial}.html.erb") ||
|
19
|
+
File.exist?(path = "#{File.dirname(__FILE__)}/../#{VIEW_PATH}/_#{partial}.html.erb") ||
|
20
|
+
File.exist?(path = "#{APP_PATH}/_#{partial}.rhtml") ||
|
21
|
+
File.exist?(path = "#{APP_PATH}/_#{partial}.erb")
|
22
|
+
render(options.merge(:file => path, :use_full_path => false))
|
23
|
+
else
|
24
|
+
""
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def inspect_model_object(model, locals={})
|
29
|
+
render_overridable(:inspect_model,
|
30
|
+
:locals => { :inspect_model => model,
|
31
|
+
:show_instance_variables => true,
|
32
|
+
:show_attributes => true }.merge(locals))
|
33
|
+
end
|
34
|
+
|
35
|
+
def inspect_value(value)
|
36
|
+
len = 512
|
37
|
+
result = object_to_yaml(value).gsub(/\n/, "\n ").strip
|
38
|
+
result = result[0,len] + "... (#{result.length-len} bytes more)" if result.length > len+20
|
39
|
+
result
|
40
|
+
end
|
41
|
+
|
42
|
+
def object_to_yaml(object)
|
43
|
+
object.to_yaml.sub(/^---\s*/m, "")
|
44
|
+
end
|
45
|
+
|
46
|
+
def exclude_raw_post_parameters?
|
47
|
+
@controller && @controller.respond_to?(:filter_parameters)
|
48
|
+
end
|
49
|
+
|
50
|
+
def filter_sensitive_post_data_parameters(parameters)
|
51
|
+
exclude_raw_post_parameters? ? @controller.__send__(:filter_parameters, parameters) : parameters
|
52
|
+
end
|
53
|
+
|
54
|
+
def filter_sensitive_post_data_from_env(env_key, env_value)
|
55
|
+
return env_value unless exclude_raw_post_parameters?
|
56
|
+
return PARAM_FILTER_REPLACEMENT if (env_key =~ /RAW_POST_DATA/i)
|
57
|
+
return @controller.__send__(:filter_parameters, {env_key => env_value}).values[0]
|
58
|
+
end
|
59
|
+
end
|
data/rails/init.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
puts "Begin Loading ExceptionNotification"
|
2
1
|
require 'rake'
|
3
2
|
require 'rake/tasklib'
|
4
3
|
require "action_mailer"
|
@@ -10,8 +9,6 @@ require "exception_notification" unless defined?(ExceptionNotification)
|
|
10
9
|
Object.class_eval do
|
11
10
|
include ExceptionNotification::Notifiable
|
12
11
|
end
|
13
|
-
puts "Object test: #{Object.respond_to?(:notifiable_noisy_environments) ? 'Pass' : 'Fail'}"
|
14
|
-
puts "Finished Loading ExceptionNotification"
|
15
12
|
|
16
13
|
#It appears that the view path is auto-added by rails... hmmm.
|
17
14
|
#if ActionController::Base.respond_to?(:append_view_path)
|
@@ -5,7 +5,7 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{super_exception_notifier}
|
8
|
-
s.version = "3.0.
|
8
|
+
s.version = "3.0.4"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Peter Boling", "Scott Windsor", "Ismael Celis", "Jacques Crocker", "Jamis Buck"]
|
@@ -39,6 +39,19 @@ Gem::Specification.new do |s|
|
|
39
39
|
"VERSION.yml",
|
40
40
|
"init.rb",
|
41
41
|
"lib/exception_notification.rb",
|
42
|
+
"lib/exception_notification/consider_local.rb",
|
43
|
+
"lib/exception_notification/custom_exception_classes.rb",
|
44
|
+
"lib/exception_notification/custom_exception_methods.rb",
|
45
|
+
"lib/exception_notification/deprecated_methods.rb",
|
46
|
+
"lib/exception_notification/exception_notifiable.rb",
|
47
|
+
"lib/exception_notification/git_blame.rb",
|
48
|
+
"lib/exception_notification/helpful_hashes.rb",
|
49
|
+
"lib/exception_notification/hooks_notifier.rb",
|
50
|
+
"lib/exception_notification/notifiable.rb",
|
51
|
+
"lib/exception_notification/notifiable_helper.rb",
|
52
|
+
"lib/exception_notification/notified_task.rb",
|
53
|
+
"lib/exception_notification/notifier.rb",
|
54
|
+
"lib/exception_notification/notifier_helper.rb",
|
42
55
|
"rails/app/views/exception_notifiable/400.html",
|
43
56
|
"rails/app/views/exception_notifiable/403.html",
|
44
57
|
"rails/app/views/exception_notifiable/404.html",
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 3
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
version: 3.0.
|
8
|
+
- 4
|
9
|
+
version: 3.0.4
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Peter Boling
|
@@ -78,6 +78,19 @@ files:
|
|
78
78
|
- VERSION.yml
|
79
79
|
- init.rb
|
80
80
|
- lib/exception_notification.rb
|
81
|
+
- lib/exception_notification/consider_local.rb
|
82
|
+
- lib/exception_notification/custom_exception_classes.rb
|
83
|
+
- lib/exception_notification/custom_exception_methods.rb
|
84
|
+
- lib/exception_notification/deprecated_methods.rb
|
85
|
+
- lib/exception_notification/exception_notifiable.rb
|
86
|
+
- lib/exception_notification/git_blame.rb
|
87
|
+
- lib/exception_notification/helpful_hashes.rb
|
88
|
+
- lib/exception_notification/hooks_notifier.rb
|
89
|
+
- lib/exception_notification/notifiable.rb
|
90
|
+
- lib/exception_notification/notifiable_helper.rb
|
91
|
+
- lib/exception_notification/notified_task.rb
|
92
|
+
- lib/exception_notification/notifier.rb
|
93
|
+
- lib/exception_notification/notifier_helper.rb
|
81
94
|
- rails/app/views/exception_notifiable/400.html
|
82
95
|
- rails/app/views/exception_notifiable/403.html
|
83
96
|
- rails/app/views/exception_notifiable/404.html
|