exception_handling 1.2.1 → 2.2.1
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.
- checksums.yaml +5 -5
- data/.ruby-version +1 -1
- data/Gemfile +17 -0
- data/Gemfile.lock +142 -102
- data/README.md +11 -2
- data/config/exception_filters.yml +2 -0
- data/exception_handling.gemspec +6 -10
- data/lib/exception_handling.rb +222 -313
- data/lib/exception_handling/exception_catalog.rb +8 -6
- data/lib/exception_handling/exception_description.rb +8 -6
- data/lib/exception_handling/exception_info.rb +272 -0
- data/lib/exception_handling/honeybadger_callbacks.rb +42 -0
- data/lib/exception_handling/log_stub_error.rb +18 -3
- data/lib/exception_handling/mailer.rb +14 -0
- data/lib/exception_handling/methods.rb +26 -8
- data/lib/exception_handling/testing.rb +2 -2
- data/lib/exception_handling/version.rb +3 -1
- data/test/helpers/controller_helpers.rb +27 -0
- data/test/helpers/exception_helpers.rb +11 -0
- data/test/test_helper.rb +42 -19
- data/test/unit/exception_handling/exception_catalog_test.rb +19 -0
- data/test/unit/exception_handling/exception_description_test.rb +12 -1
- data/test/unit/exception_handling/exception_info_test.rb +501 -0
- data/test/unit/exception_handling/honeybadger_callbacks_test.rb +85 -0
- data/test/unit/exception_handling/log_error_stub_test.rb +26 -3
- data/test/unit/exception_handling/mailer_test.rb +39 -14
- data/test/unit/exception_handling/methods_test.rb +40 -18
- data/test/unit/exception_handling_test.rb +947 -539
- data/views/exception_handling/mailer/escalate_custom.html.erb +17 -0
- data/views/exception_handling/mailer/exception_notification.html.erb +1 -1
- data/views/exception_handling/mailer/log_parser_exception_notification.html.erb +1 -1
- metadata +28 -60
@@ -15,14 +15,16 @@ module ExceptionHandling
|
|
15
15
|
private
|
16
16
|
|
17
17
|
def refresh_filters
|
18
|
-
mtime = last_modified_time
|
19
|
-
|
20
|
-
|
21
|
-
|
18
|
+
if (mtime = last_modified_time)
|
19
|
+
if @filters_last_modified_time.nil? || mtime != @filters_last_modified_time
|
20
|
+
ExceptionHandling.logger.info("Reloading filter list from: #{@filter_path}. Last loaded time: #{@filters_last_modified_time}. Last modified time: #{mtime}")
|
21
|
+
load_file
|
22
|
+
end
|
22
23
|
end
|
23
24
|
|
24
25
|
rescue => ex # any exceptions
|
25
|
-
ExceptionHandling
|
26
|
+
# DO NOT CALL ExceptionHandling.log_error because this method is called from that. It can loop and cause mayhem.
|
27
|
+
ExceptionHandling.write_exception_to_log(ex, "ExceptionCatalog#refresh_filters: #{@filter_path}", Time.now.to_i)
|
26
28
|
end
|
27
29
|
|
28
30
|
def load_file
|
@@ -34,7 +36,7 @@ module ExceptionHandling
|
|
34
36
|
end
|
35
37
|
|
36
38
|
def last_modified_time
|
37
|
-
File.mtime(@filter_path)
|
39
|
+
@filter_path && File.mtime(@filter_path)
|
38
40
|
end
|
39
41
|
end
|
40
42
|
end
|
@@ -3,13 +3,14 @@ module ExceptionHandling
|
|
3
3
|
MATCH_SECTIONS = [:error, :request, :session, :environment, :backtrace, :event_response]
|
4
4
|
|
5
5
|
CONFIGURATION_SECTIONS = {
|
6
|
-
send_email:
|
7
|
-
|
8
|
-
|
9
|
-
|
6
|
+
send_email: false, # should email be sent?
|
7
|
+
send_to_honeybadger: false, # should be sent to honeybadger?
|
8
|
+
send_metric: true, # should the metric be sent.
|
9
|
+
metric_name: nil, # Will be derived from section name if not passed
|
10
|
+
notes: nil # Will be included in exception email if set, used to keep notes and relevant links
|
10
11
|
}
|
11
12
|
|
12
|
-
attr_reader :filter_name, :send_email, :send_metric, :metric_name, :notes
|
13
|
+
attr_reader :filter_name, :send_email, :send_to_honeybadger, :send_metric, :metric_name, :notes
|
13
14
|
|
14
15
|
def initialize(filter_name, configuration)
|
15
16
|
@filter_name = filter_name
|
@@ -19,13 +20,14 @@ module ExceptionHandling
|
|
19
20
|
|
20
21
|
@configuration = CONFIGURATION_SECTIONS.merge(configuration)
|
21
22
|
@send_email = @configuration[:send_email]
|
23
|
+
@send_to_honeybadger = @configuration[:send_to_honeybadger]
|
22
24
|
@send_metric = @configuration[:send_metric]
|
23
25
|
@metric_name = (@configuration[:metric_name] || @filter_name ).to_s.gsub(" ","_")
|
24
26
|
@notes = @configuration[:notes]
|
25
27
|
|
26
28
|
regex_config = @configuration.reject { |k,v| k.in?(CONFIGURATION_SECTIONS.keys) || v.blank? }
|
27
29
|
|
28
|
-
@regexes = Hash[regex_config.map { |section, regex| [section, Regexp.new(regex,
|
30
|
+
@regexes = Hash[regex_config.map { |section, regex| [section, Regexp.new(regex, Regexp::IGNORECASE | Regexp::MULTILINE) ] }]
|
29
31
|
|
30
32
|
!@regexes.empty? or raise ArgumentError, "Filter #{filter_name} has all blank regexes: #{configuration.inspect}"
|
31
33
|
end
|
@@ -0,0 +1,272 @@
|
|
1
|
+
module ExceptionHandling
|
2
|
+
class ExceptionInfo
|
3
|
+
|
4
|
+
ENVIRONMENT_WHITELIST = [
|
5
|
+
/^HTTP_/,
|
6
|
+
/^QUERY_/,
|
7
|
+
/^REQUEST_/,
|
8
|
+
/^SERVER_/
|
9
|
+
]
|
10
|
+
|
11
|
+
ENVIRONMENT_OMIT =(
|
12
|
+
<<EOF
|
13
|
+
CONTENT_TYPE: application/x-www-form-urlencoded
|
14
|
+
GATEWAY_INTERFACE: CGI/1.2
|
15
|
+
HTTP_ACCEPT: */*
|
16
|
+
HTTP_ACCEPT: */*, text/javascript, text/html, application/xml, text/xml, */*
|
17
|
+
HTTP_ACCEPT_CHARSET: ISO-8859-1,utf-8;q=0.7,*;q=0.7
|
18
|
+
HTTP_ACCEPT_ENCODING: gzip, deflate
|
19
|
+
HTTP_ACCEPT_ENCODING: gzip,deflate
|
20
|
+
HTTP_ACCEPT_LANGUAGE: en-us
|
21
|
+
HTTP_CACHE_CONTROL: no-cache
|
22
|
+
HTTP_CONNECTION: Keep-Alive
|
23
|
+
HTTP_HOST: www.invoca.com
|
24
|
+
HTTP_MAX_FORWARDS: 10
|
25
|
+
HTTP_UA_CPU: x86
|
26
|
+
HTTP_VERSION: HTTP/1.1
|
27
|
+
HTTP_X_FORWARDED_HOST: www.invoca.com
|
28
|
+
HTTP_X_FORWARDED_SERVER: www2.invoca.com
|
29
|
+
HTTP_X_REQUESTED_WITH: XMLHttpRequest
|
30
|
+
LANG:
|
31
|
+
PATH: /sbin:/usr/sbin:/bin:/usr/bin
|
32
|
+
PWD: /
|
33
|
+
RAILS_ENV: production
|
34
|
+
RAW_POST_DATA: id=500
|
35
|
+
REMOTE_ADDR: 10.251.34.225
|
36
|
+
SCRIPT_NAME: /
|
37
|
+
SERVER_NAME: www.invoca.com
|
38
|
+
SERVER_PORT: 80
|
39
|
+
SERVER_PROTOCOL: HTTP/1.1
|
40
|
+
SERVER_SOFTWARE: Mongrel 1.1.4
|
41
|
+
SHLVL: 1
|
42
|
+
TERM: linux
|
43
|
+
TERM: xterm-color
|
44
|
+
_: /usr/bin/mongrel_cluster_ctl
|
45
|
+
EOF
|
46
|
+
).split("\n")
|
47
|
+
|
48
|
+
SECTIONS = [:request, :session, :environment, :backtrace, :event_response]
|
49
|
+
HONEYBADGER_CONTEXT_SECTIONS = [:timestamp, :error_class, :exception_context, :server, :scm_revision, :notes, :user_details, :request, :session, :environment, :backtrace, :event_response]
|
50
|
+
|
51
|
+
attr_reader :exception, :controller, :exception_context, :timestamp
|
52
|
+
|
53
|
+
def initialize(exception, exception_context, timestamp, controller = nil, data_callback = nil)
|
54
|
+
@exception = exception
|
55
|
+
@exception_context = exception_context
|
56
|
+
@timestamp = timestamp
|
57
|
+
@controller = controller || controller_from_context(exception_context)
|
58
|
+
@data_callback = data_callback
|
59
|
+
end
|
60
|
+
|
61
|
+
def data
|
62
|
+
@data ||= exception_to_data
|
63
|
+
end
|
64
|
+
|
65
|
+
def enhanced_data
|
66
|
+
@enhanced_data ||= exception_to_enhanced_data
|
67
|
+
end
|
68
|
+
|
69
|
+
def exception_description
|
70
|
+
@exception_description ||= ExceptionHandling.exception_catalog.find(enhanced_data)
|
71
|
+
end
|
72
|
+
|
73
|
+
def send_to_honeybadger?
|
74
|
+
ExceptionHandling.honeybadger? && (!exception_description || exception_description.send_to_honeybadger)
|
75
|
+
end
|
76
|
+
|
77
|
+
def honeybadger_context_data
|
78
|
+
@honeybadger_context_data ||= enhanced_data_to_honeybadger_context
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def controller_from_context(exception_context)
|
84
|
+
exception_context.is_a?(Hash) ? exception_context["action_controller.instance"] : nil
|
85
|
+
end
|
86
|
+
|
87
|
+
def exception_to_data
|
88
|
+
exception_message = @exception.message.to_s
|
89
|
+
data = ActiveSupport::HashWithIndifferentAccess.new
|
90
|
+
data[:error_class] = @exception.class.name
|
91
|
+
data[:error_string]= "#{data[:error_class]}: #{ExceptionHandling.encode_utf8(exception_message)}"
|
92
|
+
data[:timestamp] = @timestamp
|
93
|
+
data[:backtrace] = ExceptionHandling.clean_backtrace(@exception)
|
94
|
+
if @exception_context && @exception_context.is_a?(Hash)
|
95
|
+
# if we are a hash, then we got called from the DebugExceptions rack middleware filter
|
96
|
+
# and we need to do some things different to get the info we want
|
97
|
+
data[:error] = "#{data[:error_class]}: #{ExceptionHandling.encode_utf8(exception_message)}"
|
98
|
+
data[:session] = @exception_context['rack.session']
|
99
|
+
data[:environment] = @exception_context
|
100
|
+
else
|
101
|
+
data[:error] = "#{data[:error_string]}#{': ' + @exception_context.to_s unless @exception_context.blank?}"
|
102
|
+
data[:environment] = { message: @exception_context }
|
103
|
+
end
|
104
|
+
data
|
105
|
+
end
|
106
|
+
|
107
|
+
def exception_to_enhanced_data
|
108
|
+
enhanced_data = exception_to_data
|
109
|
+
extract_and_merge_controller_data(enhanced_data)
|
110
|
+
customize_from_data_callback(enhanced_data)
|
111
|
+
enhance_exception_data(enhanced_data)
|
112
|
+
normalize_exception_data(enhanced_data)
|
113
|
+
clean_exception_data(enhanced_data)
|
114
|
+
stringify_sections(enhanced_data)
|
115
|
+
|
116
|
+
description = ExceptionHandling.exception_catalog.find(enhanced_data)
|
117
|
+
description ? ActiveSupport::HashWithIndifferentAccess.new(description.exception_data.merge(enhanced_data)) : enhanced_data
|
118
|
+
end
|
119
|
+
|
120
|
+
def enhance_exception_data(data)
|
121
|
+
return if ! ExceptionHandling.custom_data_hook
|
122
|
+
begin
|
123
|
+
ExceptionHandling.custom_data_hook.call(data)
|
124
|
+
rescue Exception => ex
|
125
|
+
# can't call log_error here or we will blow the call stack
|
126
|
+
traces = ex.backtrace.join("\n")
|
127
|
+
ExceptionHandling.log_info("Unable to execute custom custom_data_hook callback. #{ExceptionHandling.encode_utf8(ex.message.to_s)} #{traces}\n")
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def normalize_exception_data(data)
|
132
|
+
if data[:location].nil?
|
133
|
+
data[:location] = {}
|
134
|
+
if data[:request] && data[:request].key?(:params)
|
135
|
+
data[:location][:controller] = data[:request][:params]['controller']
|
136
|
+
data[:location][:action] = data[:request][:params]['action']
|
137
|
+
end
|
138
|
+
end
|
139
|
+
if data[:backtrace] && data[:backtrace].first
|
140
|
+
first_line = data[:backtrace].first
|
141
|
+
|
142
|
+
# template exceptions have the line number and filename as the first element in backtrace
|
143
|
+
if matched = first_line.match( /on line #(\d*) of (.*)/i )
|
144
|
+
backtrace_hash = {}
|
145
|
+
backtrace_hash[:line] = matched[1]
|
146
|
+
backtrace_hash[:file] = matched[2]
|
147
|
+
else
|
148
|
+
backtrace_hash = Hash[* [:file, :line].zip( first_line.split( ':' )[0..1]).flatten ]
|
149
|
+
end
|
150
|
+
|
151
|
+
data[:location].merge!( backtrace_hash )
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def clean_exception_data( data )
|
156
|
+
if (as_array = data[:backtrace].to_a).size == 1
|
157
|
+
data[:backtrace] = as_array.first.to_s.split(/\n\s*/)
|
158
|
+
end
|
159
|
+
|
160
|
+
if data[:request].is_a?(Hash) && data[:request][:params].is_a?(Hash)
|
161
|
+
data[:request][:params] = deep_clean_hash(data[:request][:params])
|
162
|
+
end
|
163
|
+
|
164
|
+
if data[:environment].is_a?(Hash)
|
165
|
+
data[:environment] = clean_environment(data[:environment])
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def clean_environment(env)
|
170
|
+
Hash[ env.map do |k, v|
|
171
|
+
[k, v] if !"#{k}: #{v}".in?(ENVIRONMENT_OMIT) && ENVIRONMENT_WHITELIST.any? { |regex| k =~ regex }
|
172
|
+
end.compact ]
|
173
|
+
end
|
174
|
+
|
175
|
+
def deep_clean_hash(hash)
|
176
|
+
hash.is_a?(Hash) or return hash
|
177
|
+
|
178
|
+
hash.build_hash do |k, v|
|
179
|
+
value = v.is_a?(Hash) ? deep_clean_hash(v) : filter_sensitive_value(k, v)
|
180
|
+
[k, value]
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def filter_sensitive_value(key, value)
|
185
|
+
if key =~ /(password|oauth_token)/
|
186
|
+
"[FILTERED]"
|
187
|
+
elsif key == "rack.request.form_vars" && value.respond_to?(:match) && (captured_matches = value.match(/(.*)(password=)([^&]+)(.*)/)&.captures)
|
188
|
+
[*captured_matches[0..1], "[FILTERED]", *captured_matches[3..-1]].join
|
189
|
+
else
|
190
|
+
value
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
#
|
195
|
+
# Pull certain fields out of the controller and add to the data hash.
|
196
|
+
#
|
197
|
+
def extract_and_merge_controller_data(data)
|
198
|
+
if @controller
|
199
|
+
data[:request] = {
|
200
|
+
params: @controller.request.parameters.to_hash,
|
201
|
+
rails_root: defined?(Rails) && defined?(Rails.root) ? Rails.root : "Rails.root not defined. Is this a test environment?",
|
202
|
+
url: @controller.complete_request_uri
|
203
|
+
}
|
204
|
+
data[:environment].merge!(@controller.request.env.to_hash)
|
205
|
+
|
206
|
+
@controller.session[:fault_in_session]
|
207
|
+
data[:session] = {
|
208
|
+
key: @controller.request.session_options[:id],
|
209
|
+
data: @controller.session.to_hash
|
210
|
+
}
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
def customize_from_data_callback(data)
|
215
|
+
if @data_callback
|
216
|
+
# the expectation is that if the caller passed a block then they will be
|
217
|
+
# doing their own merge of hash values into data
|
218
|
+
begin
|
219
|
+
@data_callback.call(data)
|
220
|
+
rescue Exception => ex
|
221
|
+
data.merge!(environment: "Exception in yield: #{ex.class}:#{ex}")
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def stringify_sections(data)
|
227
|
+
SECTIONS.each { |section| add_to_s(data[section]) if data[section].is_a?(Hash) }
|
228
|
+
end
|
229
|
+
|
230
|
+
def unstringify_sections(data)
|
231
|
+
SECTIONS.each do |section|
|
232
|
+
if data[section].is_a?(Hash) && data[section].key?(:to_s)
|
233
|
+
data[section] = data[section].dup
|
234
|
+
data[section].delete(:to_s)
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def add_to_s( data_section )
|
240
|
+
data_section[:to_s] = dump_hash( data_section )
|
241
|
+
end
|
242
|
+
|
243
|
+
def dump_hash( h, indent_level = 0 )
|
244
|
+
result = ""
|
245
|
+
h.sort { |a, b| a.to_s <=> b.to_s }.each do |key, value|
|
246
|
+
result << ' ' * (2 * indent_level)
|
247
|
+
result << "#{key}:"
|
248
|
+
case value
|
249
|
+
when Hash
|
250
|
+
result << "\n" << dump_hash( value, indent_level + 1 )
|
251
|
+
else
|
252
|
+
result << " #{value}\n"
|
253
|
+
end
|
254
|
+
end unless h.nil?
|
255
|
+
result
|
256
|
+
end
|
257
|
+
|
258
|
+
def enhanced_data_to_honeybadger_context
|
259
|
+
data = enhanced_data.dup
|
260
|
+
data[:server] = ExceptionHandling.server_name
|
261
|
+
data[:exception_context] = deep_clean_hash(@exception_context) if @exception_context.present?
|
262
|
+
unstringify_sections(data)
|
263
|
+
context_data = HONEYBADGER_CONTEXT_SECTIONS.reduce({}) do |context, section|
|
264
|
+
if data[section].present?
|
265
|
+
context[section] = data[section]
|
266
|
+
end
|
267
|
+
context
|
268
|
+
end
|
269
|
+
context_data
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module ExceptionHandling
|
2
|
+
module HoneybadgerCallbacks
|
3
|
+
def self.register_callbacks
|
4
|
+
if ExceptionHandling.honeybadger?
|
5
|
+
Honeybadger.local_variable_filter(&method(:local_variable_filter))
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def self.local_variable_filter(symbol, object, filter_keys)
|
12
|
+
case object
|
13
|
+
# Honeybadger will filter these data types for us
|
14
|
+
when String, Hash, Array, Set, Numeric, TrueClass, FalseClass, NilClass
|
15
|
+
object
|
16
|
+
else # handle other Ruby objects, intended for POROs
|
17
|
+
inspection_output = object.inspect
|
18
|
+
if contains_filter_key?(filter_keys, inspection_output)
|
19
|
+
filtered_object(object)
|
20
|
+
else
|
21
|
+
inspection_output
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.contains_filter_key?(filter_keys, string)
|
27
|
+
filter_keys._?.any? { |key| string.include?(key) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.filtered_object(object)
|
31
|
+
# make the output look similar to inspect
|
32
|
+
# use [FILTERED], just like honeybadger does
|
33
|
+
if object.respond_to?(:to_pk)
|
34
|
+
"#<#{object.class.name} @pk=#{object.to_pk}, [FILTERED]>"
|
35
|
+
elsif object.respond_to?(:id)
|
36
|
+
"#<#{object.class.name} @id=#{object.id}, [FILTERED]>"
|
37
|
+
else
|
38
|
+
"#<#{object.class.name} [FILTERED]>"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -11,16 +11,31 @@ module LogErrorStub
|
|
11
11
|
stub_log_error unless respond_to?(:dont_stub_log_error) && dont_stub_log_error
|
12
12
|
end
|
13
13
|
|
14
|
+
# if used in Minitest::Test this should be called in `before teardown`
|
15
|
+
# if used in Test::Unit this should be called as the first line of `teardown`
|
14
16
|
def teardown_log_error_stub
|
15
17
|
ExceptionHandling.stub_handler = nil
|
16
18
|
return unless @exception_whitelist
|
17
|
-
|
18
|
-
|
19
|
+
|
20
|
+
@exception_whitelist.each do |pattern, match|
|
21
|
+
unless match[:expected] == match[:found]
|
22
|
+
message = "log_error expected #{match[:expected]} times with pattern: '#{pattern.is_a?(Regexp) ? pattern.source : pattern}' found #{match[:found]}"
|
23
|
+
|
24
|
+
if is_mini_test?
|
25
|
+
flunk(message)
|
26
|
+
else
|
27
|
+
add_failure(message)
|
28
|
+
end
|
29
|
+
end
|
19
30
|
end
|
20
31
|
end
|
21
32
|
|
22
33
|
attr_accessor :exception_whitelist
|
23
34
|
|
35
|
+
# for overriding when testing this module
|
36
|
+
def is_mini_test?
|
37
|
+
defined?(Minitest::Test) && self.is_a?(Minitest::Test)
|
38
|
+
end
|
24
39
|
#
|
25
40
|
# Call this function in your functional tests - usually first line after a "should" statement
|
26
41
|
# once called, you can then call expects_exception
|
@@ -81,4 +96,4 @@ module LogErrorStub
|
|
81
96
|
"------")
|
82
97
|
end
|
83
98
|
|
84
|
-
end
|
99
|
+
end
|
@@ -48,6 +48,20 @@ module ExceptionHandling
|
|
48
48
|
:subject => subject)
|
49
49
|
end
|
50
50
|
|
51
|
+
def escalate_custom(summary, data, recipients)
|
52
|
+
subject = "#{email_environment} Escalation: #{summary}"
|
53
|
+
from = sender_address.gsub('xception', 'scalation')
|
54
|
+
recipients = recipients
|
55
|
+
|
56
|
+
@summary = summary
|
57
|
+
@server = ExceptionHandling.server_name
|
58
|
+
@cleaned_data = data
|
59
|
+
|
60
|
+
mail(:from => from,
|
61
|
+
:to => recipients,
|
62
|
+
:subject => subject)
|
63
|
+
end
|
64
|
+
|
51
65
|
def log_parser_exception_notification( cleaned_data, key )
|
52
66
|
if cleaned_data.is_a?(Hash)
|
53
67
|
cleaned_data = cleaned_data.symbolize_keys
|
@@ -1,5 +1,8 @@
|
|
1
|
+
require 'active_support/concern'
|
2
|
+
|
1
3
|
module ExceptionHandling
|
2
4
|
module Methods # included on models and controllers
|
5
|
+
extend ActiveSupport::Concern
|
3
6
|
|
4
7
|
protected
|
5
8
|
|
@@ -13,7 +16,7 @@ module ExceptionHandling
|
|
13
16
|
end
|
14
17
|
|
15
18
|
def log_warning(message)
|
16
|
-
|
19
|
+
ExceptionHandling.log_warning(message)
|
17
20
|
end
|
18
21
|
|
19
22
|
def log_info(message)
|
@@ -57,23 +60,38 @@ module ExceptionHandling
|
|
57
60
|
end
|
58
61
|
end
|
59
62
|
|
60
|
-
|
61
|
-
|
63
|
+
def long_controller_action_timeout
|
64
|
+
if defined?(Rails) && Rails.respond_to?(:env) && Rails.env == 'test'
|
65
|
+
300
|
66
|
+
else
|
67
|
+
30
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
62
71
|
def set_current_controller
|
63
72
|
ExceptionHandling.current_controller = self
|
64
73
|
result = nil
|
65
74
|
time = Benchmark.measure do
|
66
75
|
result = yield
|
67
76
|
end
|
68
|
-
|
69
|
-
|
77
|
+
if time.real > self.long_controller_action_timeout && !['development', 'test'].include?(ExceptionHandling.email_environment)
|
78
|
+
name = " in #{controller_name}::#{action_name}" rescue " "
|
79
|
+
log_error( "Long controller action detected#{name} %.4fs " % time.real )
|
80
|
+
end
|
70
81
|
result
|
71
82
|
ensure
|
72
83
|
ExceptionHandling.current_controller = nil
|
73
84
|
end
|
74
85
|
|
75
|
-
|
76
|
-
|
86
|
+
included do
|
87
|
+
around_filter :set_current_controller if respond_to? :around_filter
|
88
|
+
end
|
89
|
+
|
90
|
+
class_methods do
|
91
|
+
def set_long_controller_action_timeout(timeout)
|
92
|
+
define_method(:long_controller_action_timeout) { timeout }
|
93
|
+
protected :long_controller_action_timeout
|
94
|
+
end
|
77
95
|
end
|
78
96
|
end
|
79
|
-
end
|
97
|
+
end
|