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