exception_handling 0.2.0 → 1.0.0
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 +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
|