exception_handling 0.2.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/Gemfile.lock +47 -2
- data/README.md +109 -0
- data/Rakefile +12 -4
- data/exception_handling.gemspec +23 -19
- data/lib/exception_handling/log_stub_error.rb +84 -0
- data/lib/{exception_handling_mailer.rb → exception_handling/mailer.rb} +1 -1
- data/lib/exception_handling/methods.rb +69 -0
- data/lib/exception_handling/testing.rb +65 -0
- data/lib/exception_handling/version.rb +1 -1
- data/lib/exception_handling.rb +127 -91
- data/test/test_helper.rb +55 -2
- data/test/unit/exception_handling/log_error_stub_test.rb +83 -0
- data/test/unit/exception_handling/mailer_test.rb +79 -0
- data/test/unit/exception_handling/methods_test.rb +59 -0
- data/test/unit/exception_handling_test.rb +717 -0
- data/views/exception_handling/mailer/exception_notification.html.erb +4 -1
- metadata +62 -13
- data/README +0 -1
- data/test/exception_handling_test.rb +0 -977
- data/test/mocha_patch.rb +0 -63
data/lib/exception_handling.rb
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
require 'timeout'
|
2
2
|
require 'active_support'
|
3
3
|
require 'active_support/core_ext/hash'
|
4
|
-
require "#{File.dirname(__FILE__)}/exception_handling_mailer"
|
5
4
|
|
6
|
-
|
5
|
+
require 'invoca/utils'
|
7
6
|
|
8
|
-
|
7
|
+
require "exception_handling/mailer"
|
8
|
+
require "exception_handling/methods"
|
9
|
+
require "exception_handling/log_stub_error"
|
9
10
|
|
10
|
-
|
11
|
-
require 'em/protocols/smtpclient'
|
12
|
-
end
|
11
|
+
_ = ActiveSupport::HashWithIndifferentAccess
|
13
12
|
|
14
13
|
module ExceptionHandling # never included
|
15
14
|
|
@@ -20,9 +19,7 @@ module ExceptionHandling # never included
|
|
20
19
|
SUMMARY_THRESHOLD = 5
|
21
20
|
SUMMARY_PERIOD = 60*60 # 1.hour
|
22
21
|
|
23
|
-
|
24
22
|
SECTIONS = [:request, :session, :environment, :backtrace, :event_response]
|
25
|
-
EXCEPTION_FILTER_LIST_PATH = "#{defined?(Rails) ? Rails.root : '.'}/config/exception_filters.yml"
|
26
23
|
|
27
24
|
ENVIRONMENT_WHITELIST = [
|
28
25
|
/^HTTP_/,
|
@@ -71,19 +68,77 @@ EOF
|
|
71
68
|
|
72
69
|
AUTHENTICATION_HEADERS = ['HTTP_AUTHORIZATION','X-HTTP_AUTHORIZATION','X_HTTP_AUTHORIZATION','REDIRECT_X_HTTP_AUTHORIZATION']
|
73
70
|
|
74
|
-
@logger = Rails.logger if defined?(Rails)
|
75
|
-
|
76
|
-
|
77
71
|
class << self
|
78
|
-
|
72
|
+
|
73
|
+
#
|
74
|
+
# required settings
|
75
|
+
#
|
79
76
|
attr_accessor :server_name
|
80
77
|
attr_accessor :sender_address
|
81
78
|
attr_accessor :exception_recipients
|
79
|
+
attr_accessor :logger
|
80
|
+
|
81
|
+
def server_name
|
82
|
+
@server_name or raise ArgumentError, "You must assign a value to #{self.name}.server_name"
|
83
|
+
end
|
84
|
+
|
85
|
+
def sender_address
|
86
|
+
@sender_address or raise ArgumentError, "You must assign a value to #{self.name}.sender_address"
|
87
|
+
end
|
88
|
+
|
89
|
+
def exception_recipients
|
90
|
+
@exception_recipients or raise ArgumentError, "You must assign a value to #{self.name}.exception_recipients"
|
91
|
+
end
|
92
|
+
|
93
|
+
def logger
|
94
|
+
@logger or raise ArgumentError, "You must assign a value to #{self.name}.logger"
|
95
|
+
end
|
96
|
+
|
97
|
+
#
|
98
|
+
# optional settings
|
99
|
+
#
|
100
|
+
attr_accessor :escalation_recipients
|
101
|
+
attr_accessor :email_environment
|
102
|
+
attr_accessor :filter_list_filename
|
103
|
+
attr_accessor :mailer_send_enabled
|
104
|
+
attr_accessor :eventmachine_safe
|
105
|
+
attr_accessor :eventmachine_synchrony
|
106
|
+
attr_accessor :custom_data_hook
|
107
|
+
attr_accessor :post_log_error_hook
|
108
|
+
attr_accessor :stub_handler
|
109
|
+
|
110
|
+
@filter_list_filename = "./config/exception_filters.yml"
|
111
|
+
@mailer_send_enabled = true
|
112
|
+
@email_environment = ""
|
113
|
+
@eventmachine_safe = false
|
114
|
+
@eventmachine_synchrony = false
|
115
|
+
|
116
|
+
# set this for operation within an eventmachine reactor
|
117
|
+
def eventmachine_safe=(bool)
|
118
|
+
if bool != true && bool != false
|
119
|
+
raise ArgumentError, "#{self.name}.eventmachine_safe must be a boolean."
|
120
|
+
end
|
121
|
+
if bool
|
122
|
+
require 'eventmachine'
|
123
|
+
require 'em/protocols/smtpclient'
|
124
|
+
end
|
125
|
+
@eventmachine_safe = bool
|
126
|
+
end
|
82
127
|
|
128
|
+
# set this for EM::Synchrony async operation
|
129
|
+
def eventmachine_synchrony=(bool)
|
130
|
+
if bool != true && bool != false
|
131
|
+
raise ArgumentError, "#{self.name}.eventmachine_synchrony must be a boolean."
|
132
|
+
end
|
133
|
+
@eventmachine_synchrony = bool
|
134
|
+
end
|
135
|
+
|
136
|
+
#
|
137
|
+
# internal settings (don't set directly)
|
138
|
+
#
|
83
139
|
attr_accessor :current_controller
|
84
140
|
attr_accessor :last_exception_timestamp
|
85
141
|
attr_accessor :periodic_exception_intervals
|
86
|
-
attr_accessor :logger
|
87
142
|
|
88
143
|
#
|
89
144
|
# Gets called by Rack Middleware: DebugExceptions or ShowExceptions
|
@@ -97,6 +152,10 @@ EOF
|
|
97
152
|
timestamp = set_log_error_timestamp
|
98
153
|
exception_data = exception_to_data(exception, env, timestamp)
|
99
154
|
|
155
|
+
if stub_handler
|
156
|
+
return stub_handler.handle_stub_log_error(exception_data)
|
157
|
+
end
|
158
|
+
|
100
159
|
# TODO: add a more interesting custom description, like:
|
101
160
|
# custom_description = ": caught and processed by Rack middleware filter #{rack_filter}"
|
102
161
|
# which would be nice, but would also require changing quite a few tests
|
@@ -106,7 +165,10 @@ EOF
|
|
106
165
|
if should_send_email?
|
107
166
|
controller = env['action_controller.instance']
|
108
167
|
# controller may not exist in some cases (like most 404 errors)
|
109
|
-
|
168
|
+
if controller
|
169
|
+
extract_and_merge_controller_data(controller, exception_data)
|
170
|
+
controller.session["last_exception_timestamp"] = ExceptionHandling.last_exception_timestamp
|
171
|
+
end
|
110
172
|
log_error_email(exception_data, exception)
|
111
173
|
end
|
112
174
|
end
|
@@ -125,6 +187,10 @@ EOF
|
|
125
187
|
timestamp = set_log_error_timestamp
|
126
188
|
data = exception_to_data(ex, exception_context, timestamp)
|
127
189
|
|
190
|
+
if stub_handler
|
191
|
+
return stub_handler.handle_stub_log_error(data)
|
192
|
+
end
|
193
|
+
|
128
194
|
write_exception_to_log(ex, exception_context, timestamp)
|
129
195
|
|
130
196
|
if treat_as_local
|
@@ -151,6 +217,8 @@ EOF
|
|
151
217
|
log_error_email(data, ex)
|
152
218
|
end
|
153
219
|
|
220
|
+
rescue LogErrorStub::UnexpectedExceptionLogged, LogErrorStub::ExpectedExceptionNotLogged
|
221
|
+
raise
|
154
222
|
rescue Exception => ex
|
155
223
|
$stderr.puts("ExceptionHandling.log_error rescued exception while logging #{exception_context}: #{exception_or_string}:\n#{ex.class}: #{ex}\n#{ex.backtrace.join("\n")}")
|
156
224
|
write_exception_to_log(ex, "ExceptionHandling.log_error rescued exception while logging #{exception_context}: #{exception_or_string}", timestamp)
|
@@ -178,7 +246,7 @@ EOF
|
|
178
246
|
def extract_and_merge_controller_data(controller, data)
|
179
247
|
data[:request] = {
|
180
248
|
:params => controller.request.parameters.to_hash,
|
181
|
-
:rails_root => Rails.root,
|
249
|
+
:rails_root => defined?(Rails) ? Rails.root : "Rails.root not defined. Is this a test environment?",
|
182
250
|
:url => controller.complete_request_uri
|
183
251
|
}
|
184
252
|
data[:environment].merge!(controller.request.env.to_hash)
|
@@ -217,12 +285,23 @@ EOF
|
|
217
285
|
log_error ex, exception_context
|
218
286
|
end
|
219
287
|
|
220
|
-
def
|
288
|
+
def escalate_error(exception_or_string, email_subject)
|
289
|
+
ex = make_exception(exception_or_string)
|
290
|
+
log_error(ex)
|
291
|
+
escalate(email_subject, ex, ExceptionHandling.last_exception_timestamp)
|
292
|
+
end
|
293
|
+
|
294
|
+
def escalate_warning(message, email_subject)
|
295
|
+
ex = Warning.new(message)
|
296
|
+
log_error(ex)
|
297
|
+
escalate(email_subject, ex, ExceptionHandling.last_exception_timestamp)
|
298
|
+
end
|
299
|
+
|
300
|
+
def ensure_escalation(email_subject)
|
221
301
|
begin
|
222
302
|
yield
|
223
303
|
rescue => ex
|
224
|
-
|
225
|
-
escalate(email_subject, ex, ExceptionHandling.last_exception_timestamp)
|
304
|
+
escalate_error(ex, email_subject)
|
226
305
|
nil
|
227
306
|
end
|
228
307
|
end
|
@@ -232,7 +311,7 @@ EOF
|
|
232
311
|
end
|
233
312
|
|
234
313
|
def should_send_email?
|
235
|
-
|
314
|
+
ExceptionHandling.mailer_send_enabled
|
236
315
|
end
|
237
316
|
|
238
317
|
def trace_timing(description)
|
@@ -253,9 +332,14 @@ EOF
|
|
253
332
|
end
|
254
333
|
end
|
255
334
|
|
256
|
-
# TODO: fix test to not use this.
|
257
335
|
def enhance_exception_data(data)
|
258
|
-
|
336
|
+
return if ! ExceptionHandling.custom_data_hook
|
337
|
+
begin
|
338
|
+
ExceptionHandling.custom_data_hook.call(data)
|
339
|
+
rescue Exception => ex
|
340
|
+
# can't call log_error here or we will blow the call stack
|
341
|
+
log_info( "Unable to execute custom custom_data_hook callback. #{ex.message} #{ex.backtrace.each {|l| "#{l}\n"}}" )
|
342
|
+
end
|
259
343
|
end
|
260
344
|
|
261
345
|
private
|
@@ -281,18 +365,30 @@ EOF
|
|
281
365
|
Errplane.transmit(exc, :custom_data => data) unless exc.is_a?(Warning)
|
282
366
|
end
|
283
367
|
|
368
|
+
execute_custom_log_error_callback(data, exc)
|
369
|
+
|
284
370
|
nil
|
285
371
|
end
|
286
372
|
|
373
|
+
def execute_custom_log_error_callback(exception_data, exception)
|
374
|
+
return if ! ExceptionHandling.post_log_error_hook
|
375
|
+
begin
|
376
|
+
ExceptionHandling.post_log_error_hook.call(exception_data, exception)
|
377
|
+
rescue Exception => ex
|
378
|
+
# can't call log_error here or we will blow the call stack
|
379
|
+
log_info( "Unable to execute custom log_error callback. #{ex.message} #{ex.backtrace.each {|l| "#{l}\n"}}" )
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
287
383
|
def escalate( email_subject, ex, timestamp )
|
288
384
|
data = exception_to_data( ex, nil, timestamp )
|
289
385
|
deliver(ExceptionHandling::Mailer.escalation_notification(email_subject, data))
|
290
386
|
end
|
291
387
|
|
292
388
|
def deliver(mail_object)
|
293
|
-
if
|
389
|
+
if ExceptionHandling.eventmachine_safe
|
294
390
|
EventMachine.schedule do # in case we're running outside the reactor
|
295
|
-
async_send_method =
|
391
|
+
async_send_method = ExceptionHandling.eventmachine_synchrony ? :asend : :send
|
296
392
|
smtp_settings = ActionMailer::Base.smtp_settings
|
297
393
|
send_deferrable = EventMachine::Protocols::SmtpClient.__send__(
|
298
394
|
async_send_method,
|
@@ -303,7 +399,7 @@ EOF
|
|
303
399
|
:auth => {:type=>:plain, :username=>smtp_settings[:user_name], :password=>smtp_settings[:password]},
|
304
400
|
:from => mail_object['from'].to_s,
|
305
401
|
:to => mail_object['to'].to_s,
|
306
|
-
:
|
402
|
+
:content => "#{mail_object}.\r\n"
|
307
403
|
}
|
308
404
|
)
|
309
405
|
send_deferrable.errback { |err| ExceptionHandling.logger.fatal("Failed to email by SMTP: #{err.inspect}") }
|
@@ -320,7 +416,7 @@ EOF
|
|
320
416
|
yield
|
321
417
|
end
|
322
418
|
rescue StandardError, MailerTimeout => ex
|
323
|
-
|
419
|
+
#$stderr.puts("ExceptionHandling::safe_email_deliver rescued: #{ex.class}: #{ex}\n#{ex.backtrace.join("\n")}")
|
324
420
|
log_error( ex, "ExceptionHandling::safe_email_deliver", nil, true )
|
325
421
|
end
|
326
422
|
|
@@ -341,10 +437,10 @@ EOF
|
|
341
437
|
def normalize_exception_data( data )
|
342
438
|
if data[:location].nil?
|
343
439
|
data[:location] = {}
|
344
|
-
|
345
|
-
|
346
|
-
data[:location][:action] =
|
347
|
-
|
440
|
+
if data[:request] && data[:request].key?( :params )
|
441
|
+
data[:location][:controller] = data[:request][:params]['controller']
|
442
|
+
data[:location][:action] = data[:request][:params]['action']
|
443
|
+
end
|
348
444
|
end
|
349
445
|
if data[:backtrace] && data[:backtrace].first
|
350
446
|
first_line = data[:backtrace].first
|
@@ -375,7 +471,7 @@ EOF
|
|
375
471
|
end
|
376
472
|
|
377
473
|
def exception_filters
|
378
|
-
@exception_filters ||= ExceptionFilters.new(
|
474
|
+
@exception_filters ||= ExceptionFilters.new( ExceptionHandling.filter_list_filename )
|
379
475
|
end
|
380
476
|
|
381
477
|
def clean_backtrace(exception)
|
@@ -464,7 +560,7 @@ EOF
|
|
464
560
|
data[:session] = exception_context['rack.session']
|
465
561
|
data[:environment] = exception_context
|
466
562
|
else
|
467
|
-
data[:error] = "#{data[:error_string]}#{': ' + exception_context unless exception_context.blank?}"
|
563
|
+
data[:error] = "#{data[:error_string]}#{': ' + exception_context.to_s unless exception_context.blank?}"
|
468
564
|
data[:environment] = { :message => exception_context }
|
469
565
|
end
|
470
566
|
data
|
@@ -573,66 +669,6 @@ EOF
|
|
573
669
|
def last_modified_time
|
574
670
|
File.mtime( @filter_path )
|
575
671
|
end
|
576
|
-
end
|
577
|
-
|
578
|
-
|
579
|
-
public
|
580
|
-
|
581
|
-
module Methods # included on models and controllers
|
582
|
-
protected
|
583
|
-
def log_error(exception_or_string, exception_context = '')
|
584
|
-
controller = self if respond_to?(:request) && respond_to?(:session)
|
585
|
-
ExceptionHandling.log_error(exception_or_string, exception_context, controller)
|
586
|
-
end
|
587
672
|
|
588
|
-
def log_error_rack(exception_or_string, exception_context = '', rack_filter = '')
|
589
|
-
ExceptionHandling.log_error_rack(exception_or_string, exception_context, controller)
|
590
|
-
end
|
591
|
-
|
592
|
-
def log_warning(message)
|
593
|
-
log_error(Warning.new(message))
|
594
|
-
end
|
595
|
-
|
596
|
-
def log_info(message)
|
597
|
-
ExceptionHandling.logger.info( message )
|
598
|
-
end
|
599
|
-
|
600
|
-
def log_debug(message)
|
601
|
-
ExceptionHandling.logger.debug( message )
|
602
|
-
end
|
603
|
-
|
604
|
-
def ensure_safe(exception_context = "")
|
605
|
-
begin
|
606
|
-
yield
|
607
|
-
rescue => ex
|
608
|
-
log_error ex, exception_context
|
609
|
-
nil
|
610
|
-
end
|
611
|
-
end
|
612
|
-
|
613
|
-
def ensure_escalation(*args)
|
614
|
-
ExceptionHandling.ensure_escalation(*args) do
|
615
|
-
yield
|
616
|
-
end
|
617
|
-
end
|
618
|
-
|
619
|
-
# Store aside the current controller when included
|
620
|
-
LONG_REQUEST_SECONDS = (defined?(Rails) && Rails.env == 'test' ? 300 : 30)
|
621
|
-
def set_current_controller
|
622
|
-
ExceptionHandling.current_controller = self
|
623
|
-
result = nil
|
624
|
-
time = Benchmark.measure do
|
625
|
-
result = yield
|
626
|
-
end
|
627
|
-
name = " in #{controller_name}::#{action_name}" rescue " "
|
628
|
-
log_error( "Long controller action detected#{name} %.4fs " % time.real ) if time.real > LONG_REQUEST_SECONDS && !['development', 'test'].include?(Rails.env)
|
629
|
-
result
|
630
|
-
ensure
|
631
|
-
ExceptionHandling.current_controller = nil
|
632
|
-
end
|
633
|
-
|
634
|
-
def self.included( controller )
|
635
|
-
controller.around_filter :set_current_controller if controller.respond_to? :around_filter
|
636
|
-
end
|
637
673
|
end
|
638
674
|
end
|
data/test/test_helper.rb
CHANGED
@@ -2,9 +2,44 @@ require 'active_support'
|
|
2
2
|
require 'active_support/time'
|
3
3
|
require 'active_support/test_case'
|
4
4
|
require 'action_mailer'
|
5
|
+
require 'hobo_support'
|
5
6
|
require 'shoulda'
|
6
|
-
require '
|
7
|
-
require '
|
7
|
+
require 'rr'
|
8
|
+
require 'minitest/autorun'
|
9
|
+
require 'pry'
|
10
|
+
|
11
|
+
require 'exception_handling'
|
12
|
+
require 'exception_handling/testing'
|
13
|
+
|
14
|
+
class LoggerStub
|
15
|
+
attr_accessor :logged
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
clear
|
19
|
+
end
|
20
|
+
|
21
|
+
def info(message)
|
22
|
+
logged << message
|
23
|
+
end
|
24
|
+
|
25
|
+
def warn(message)
|
26
|
+
logged << message
|
27
|
+
end
|
28
|
+
|
29
|
+
def fatal(message)
|
30
|
+
logged << message
|
31
|
+
end
|
32
|
+
|
33
|
+
def clear
|
34
|
+
@logged = []
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
ExceptionHandling.logger = LoggerStub.new
|
39
|
+
|
40
|
+
def dont_stub_log_error
|
41
|
+
true
|
42
|
+
end
|
8
43
|
|
9
44
|
ActionMailer::Base.delivery_method = :test
|
10
45
|
|
@@ -22,6 +57,16 @@ class ActiveSupport::TestCase
|
|
22
57
|
Time.now_override = nil
|
23
58
|
|
24
59
|
ActionMailer::Base.deliveries.clear
|
60
|
+
|
61
|
+
ExceptionHandling.email_environment = 'Test'
|
62
|
+
ExceptionHandling.sender_address = 'server@example.com'
|
63
|
+
ExceptionHandling.exception_recipients = 'exceptions@example.com'
|
64
|
+
ExceptionHandling.escalation_recipients = 'escalation@example.com'
|
65
|
+
ExceptionHandling.server_name = 'server'
|
66
|
+
ExceptionHandling.mailer_send_enabled = true
|
67
|
+
ExceptionHandling.filter_list_filename = "./config/exception_filters.yml"
|
68
|
+
ExceptionHandling.eventmachine_safe = false
|
69
|
+
ExceptionHandling.eventmachine_synchrony = false
|
25
70
|
end
|
26
71
|
|
27
72
|
teardown do
|
@@ -68,6 +113,14 @@ class ActiveSupport::TestCase
|
|
68
113
|
end
|
69
114
|
end
|
70
115
|
|
116
|
+
def assert_equal_with_diff arg1, arg2, msg = ''
|
117
|
+
if arg1 == arg2
|
118
|
+
assert true # To keep the assertion count accurate
|
119
|
+
else
|
120
|
+
assert_equal arg1, arg2, "#{msg}\n#{Diff.compare(arg1, arg2)}"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
71
124
|
class Time
|
72
125
|
class << self
|
73
126
|
attr_reader :now_override
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require File.expand_path('../../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
module ExceptionHandling
|
4
|
+
class LogErrorStubTest < ActiveSupport::TestCase
|
5
|
+
|
6
|
+
include LogErrorStub
|
7
|
+
|
8
|
+
context "while running tests" do
|
9
|
+
setup do
|
10
|
+
setup_log_error_stub
|
11
|
+
end
|
12
|
+
|
13
|
+
teardown do
|
14
|
+
teardown_log_error_stub
|
15
|
+
end
|
16
|
+
|
17
|
+
should "raise an error when log_error and log_warning are called" do
|
18
|
+
begin
|
19
|
+
ExceptionHandling.log_error("Something happened")
|
20
|
+
flunk
|
21
|
+
rescue Exception => ex #LogErrorStub::UnexpectedExceptionLogged => ex
|
22
|
+
assert ex.to_s.starts_with?("StandardError: Something happened"), ex.to_s
|
23
|
+
end
|
24
|
+
|
25
|
+
begin
|
26
|
+
class ::RaisedError < StandardError; end
|
27
|
+
raise ::RaisedError, "This should raise"
|
28
|
+
rescue => ex
|
29
|
+
begin
|
30
|
+
ExceptionHandling.log_error(ex)
|
31
|
+
rescue LogErrorStub::UnexpectedExceptionLogged => ex_inner
|
32
|
+
assert ex_inner.to_s.starts_with?("RaisedError: This should raise"), ex_inner.to_s
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
should "allow for the regex specification of an expected exception to be ignored" do
|
38
|
+
exception_pattern = /StandardError: This is a test error/
|
39
|
+
assert_nil exception_whitelist # test that exception expectations are cleared
|
40
|
+
expects_exception(exception_pattern)
|
41
|
+
assert_equal exception_pattern, exception_whitelist[0][0]
|
42
|
+
begin
|
43
|
+
ExceptionHandling.log_error("This is a test error")
|
44
|
+
rescue => ex
|
45
|
+
flunk # Shouldn't raise an error in this case
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
should "allow for the string specification of an expected exception to be ignored" do
|
50
|
+
exception_pattern = "StandardError: This is a test error"
|
51
|
+
assert_nil exception_whitelist # test that exception expectations are cleared
|
52
|
+
expects_exception(exception_pattern)
|
53
|
+
assert_equal exception_pattern, exception_whitelist[0][0]
|
54
|
+
begin
|
55
|
+
ExceptionHandling.log_error("This is a test error")
|
56
|
+
rescue => ex
|
57
|
+
flunk # Shouldn't raise an error in this case
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
should "allow multiple errors to be ignored" do
|
62
|
+
class IgnoredError < StandardError; end
|
63
|
+
assert_nil exception_whitelist # test that exception expectations are cleared
|
64
|
+
expects_exception(/StandardError: This is a test error/)
|
65
|
+
expects_exception(/IgnoredError: This should be ignored/)
|
66
|
+
ExceptionHandling.log_error("This is a test error")
|
67
|
+
begin
|
68
|
+
raise IgnoredError, "This should be ignored"
|
69
|
+
rescue IgnoredError => ex
|
70
|
+
ExceptionHandling.log_error(ex)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
should "expect exception twice if declared twice" do
|
75
|
+
expects_exception(/StandardError: ERROR: I love lamp/)
|
76
|
+
expects_exception(/StandardError: ERROR: I love lamp/)
|
77
|
+
ExceptionHandling.log_error("ERROR: I love lamp")
|
78
|
+
ExceptionHandling.log_error("ERROR: I love lamp")
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require File.expand_path('../../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
module ExceptionHandling
|
4
|
+
class MailerTest < ActionMailer::TestCase
|
5
|
+
|
6
|
+
include ActionDispatch::Assertions::SelectorAssertions
|
7
|
+
tests ExceptionHandling::Mailer
|
8
|
+
|
9
|
+
def dont_stub_log_error
|
10
|
+
true
|
11
|
+
end
|
12
|
+
|
13
|
+
context "ExceptionHandling::Mailer" do
|
14
|
+
setup do
|
15
|
+
ExceptionHandling.email_environment = 'Test'
|
16
|
+
ExceptionHandling.sender_address = %("Test Exception Mailer" <null_exception@invoca.com>)
|
17
|
+
ExceptionHandling.exception_recipients = ['test_exception@invoca.com']
|
18
|
+
ExceptionHandling.escalation_recipients = ['test_escalation@invoca.com']
|
19
|
+
end
|
20
|
+
|
21
|
+
should "deliver" do
|
22
|
+
#ActionMailer::Base.delivery_method = :smtp
|
23
|
+
result = ExceptionHandling::Mailer.exception_notification({ :error => "Test Error."}).deliver
|
24
|
+
assert_match /Test Error./, result.body.to_s
|
25
|
+
assert_equal_with_diff ['test_exception@invoca.com'], result.to
|
26
|
+
assert_emails 1
|
27
|
+
end
|
28
|
+
|
29
|
+
context "log_parser_exception_notification" do
|
30
|
+
should "send with string" do
|
31
|
+
result = ExceptionHandling::Mailer.log_parser_exception_notification("This is my fake error", "My Fake Subj").deliver
|
32
|
+
assert_equal "Test exception: My Fake Subj: This is my fake error", result.subject
|
33
|
+
assert_match(/This is my fake error/, result.body.to_s)
|
34
|
+
assert_emails 1
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context "escalation_notification" do
|
39
|
+
should "send all the information" do
|
40
|
+
ExceptionHandling.email_environment = 'Staging Full'
|
41
|
+
ExceptionHandling.server_name = 'test-fe3'
|
42
|
+
|
43
|
+
ExceptionHandling::Mailer.escalation_notification("Your Favorite <b>Feature<b> Failed", :error_string => "It failed because of an error\n <i>More Info<i>", :timestamp => 1234567 ).deliver
|
44
|
+
|
45
|
+
assert_emails 1
|
46
|
+
result = ActionMailer::Base.deliveries.last
|
47
|
+
body_html = HTML::Document.new(result.body.to_s)
|
48
|
+
assert_equal_with_diff ['test_escalation@invoca.com'], result.to
|
49
|
+
assert_equal ["Test Escalation Mailer <null_escalation@invoca.com>"], result[:from].formatted
|
50
|
+
assert_equal "Staging Full Escalation: Your Favorite <b>Feature<b> Failed", result.subject
|
51
|
+
assert_select body_html.root, "html" do
|
52
|
+
assert_select "title", "Exception Escalation"
|
53
|
+
assert_select "body br", { :count => 4 }, result.body.to_s # plus 1 for the multiline summary
|
54
|
+
assert_select "body h3", "Your Favorite <b>Feature<b> Failed", result.body.to_s
|
55
|
+
assert_select "body", /1234567/
|
56
|
+
assert_select "body", /It failed because of an error\n <i>More Info<i>/
|
57
|
+
assert_select "body", /test-fe3/
|
58
|
+
#assert_select "body", /#{Web::Application::GIT_REVISION}/
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
should "use defaults for missing fields" do
|
63
|
+
result = ExceptionHandling::Mailer.escalation_notification("Your Favorite Feature Failed", :error_string => "It failed because of an error\n More Info")
|
64
|
+
body_html = HTML::Document.new(result.body.to_s)
|
65
|
+
|
66
|
+
assert_equal_with_diff ['test_escalation@invoca.com'], result.to
|
67
|
+
assert_equal ["null_escalation@invoca.com"], result.from
|
68
|
+
assert_equal 'Test Escalation: Your Favorite Feature Failed', result.subject
|
69
|
+
assert_select body_html.root, "html" do
|
70
|
+
assert_select "body i", true, result.body.to_s do |is|
|
71
|
+
assert_select is[0], "i", 'no error #'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require File.expand_path('../../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
require "exception_handling/testing"
|
4
|
+
|
5
|
+
module ExceptionHandling
|
6
|
+
class MethodsTest < ActiveSupport::TestCase
|
7
|
+
|
8
|
+
def dont_stub_log_error
|
9
|
+
true
|
10
|
+
end
|
11
|
+
|
12
|
+
context "ExceptionHandling.Methods" do
|
13
|
+
setup do
|
14
|
+
@controller = Testing::ControllerStub.new
|
15
|
+
ExceptionHandling.stub_handler = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
teardown do
|
19
|
+
Time.now_override = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
should "set the around filter" do
|
23
|
+
assert_equal :set_current_controller, Testing::ControllerStub.around_filter_method
|
24
|
+
assert_nil ExceptionHandling.current_controller
|
25
|
+
@controller.simulate_around_filter( ) do
|
26
|
+
assert_equal @controller, ExceptionHandling.current_controller
|
27
|
+
end
|
28
|
+
assert_nil ExceptionHandling.current_controller
|
29
|
+
end
|
30
|
+
|
31
|
+
should "use the current_controller when available" do
|
32
|
+
mock(ExceptionHandling.logger).fatal(/blah/)
|
33
|
+
@controller.simulate_around_filter do
|
34
|
+
ExceptionHandling.log_error( ArgumentError.new("blah") )
|
35
|
+
mail = ActionMailer::Base.deliveries.last
|
36
|
+
assert_match( @controller.request.request_uri, mail.body.to_s )
|
37
|
+
# assert_match( Username.first.username.to_s, mail.body.to_s ) if defined?(Username)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
should "report long running controller action" do
|
42
|
+
# This was the original stub:
|
43
|
+
# Rails.expects(:env).times(2).returns('production')
|
44
|
+
# but this stubbing approach no longer works
|
45
|
+
# Rails is setting the long controller timeout on module load
|
46
|
+
# in exception_handling.rb - that happens before this test ever gets run
|
47
|
+
# we can set Rails.env here, which we do, because it affects part of the real-time
|
48
|
+
# logic check for whether to raise (which we want). To overcome the setting
|
49
|
+
# on the long controller timeout I have set the Time.now_override to 1.day.from_now
|
50
|
+
# instead of 1.hour.from_now
|
51
|
+
mock(ExceptionHandling).log_error(/Long controller action detected in #{@controller.class.name.split("::").last}::test_action/, anything, anything)
|
52
|
+
@controller.simulate_around_filter( ) do
|
53
|
+
Time.now_override = 1.day.from_now
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|