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