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
@@ -0,0 +1,85 @@
|
|
1
|
+
require File.expand_path('../../../test_helper', __FILE__)
|
2
|
+
|
3
|
+
module ExceptionHandling
|
4
|
+
class HoneybadgerCallbacksTest < ActiveSupport::TestCase
|
5
|
+
|
6
|
+
class TestPoroWithAttribute
|
7
|
+
attr_reader :test_attribute
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@test_attribute = 'test'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class TestPoroWithFilteredAttribute
|
15
|
+
attr_reader :password
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@password = 'secret'
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class TestPoroWithFilteredAttributeAndId < TestPoroWithFilteredAttribute
|
23
|
+
attr_reader :id
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
super
|
27
|
+
@id = 1
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class TestPoroWithFilteredAttributePkAndId < TestPoroWithFilteredAttributeAndId
|
32
|
+
def to_pk
|
33
|
+
'TestPoroWithFilteredAttributePkAndId_1'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "register_callbacks" do
|
38
|
+
should "store the callbacks in the honeybadger object" do
|
39
|
+
HoneybadgerCallbacks.register_callbacks
|
40
|
+
result = Honeybadger.config.local_variable_filter.call(:variable_name, 'test', [])
|
41
|
+
assert_equal('test', result)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "local_variable_filter" do
|
46
|
+
should "not inspect String, Hash, Array, Set, Numeric, TrueClass, FalseClass, NilClass" do
|
47
|
+
[
|
48
|
+
['test', String],
|
49
|
+
[{a: 1}, Hash],
|
50
|
+
[[1,2], Array],
|
51
|
+
[Set.new([1,2]), Set],
|
52
|
+
[4.5, Numeric],
|
53
|
+
[true, TrueClass],
|
54
|
+
[false, FalseClass],
|
55
|
+
[nil, NilClass]
|
56
|
+
].each do |object, expected_class|
|
57
|
+
result = HoneybadgerCallbacks.local_variable_filter(:variable_name, object, [])
|
58
|
+
assert result.is_a?(expected_class), "Expected #{expected_class.name} but got #{result.class.name}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
should "inspect other classes" do
|
63
|
+
result = HoneybadgerCallbacks.local_variable_filter(:variable_name, TestPoroWithAttribute.new, ['password'])
|
64
|
+
assert_match(/#<ExceptionHandling::HoneybadgerCallbacksTest::TestPoroWithAttribute:.* @test_attribute="test">/, result)
|
65
|
+
end
|
66
|
+
|
67
|
+
context "not inspect objects that contain filter keys" do
|
68
|
+
should "use to_pk if available, even if id is available" do
|
69
|
+
result = HoneybadgerCallbacks.local_variable_filter(:variable_name, TestPoroWithFilteredAttributePkAndId.new, ['password'])
|
70
|
+
assert_match(/#<ExceptionHandling::HoneybadgerCallbacksTest::TestPoroWithFilteredAttributePkAndId @pk=TestPoroWithFilteredAttributePkAndId_1, \[FILTERED\]>/, result)
|
71
|
+
end
|
72
|
+
|
73
|
+
should "use id if to_pk is not available" do
|
74
|
+
result = HoneybadgerCallbacks.local_variable_filter(:variable_name, TestPoroWithFilteredAttributeAndId.new, ['password'])
|
75
|
+
assert_match(/#<ExceptionHandling::HoneybadgerCallbacksTest::TestPoroWithFilteredAttributeAndId @id=1, \[FILTERED\]>/, result)
|
76
|
+
end
|
77
|
+
|
78
|
+
should "print the object name if no id or to_pk" do
|
79
|
+
result = HoneybadgerCallbacks.local_variable_filter(:variable_name, TestPoroWithFilteredAttribute.new, ['password'])
|
80
|
+
assert_match(/#<ExceptionHandling::HoneybadgerCallbacksTest::TestPoroWithFilteredAttribute \[FILTERED\]>/, result)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -41,7 +41,7 @@ module ExceptionHandling
|
|
41
41
|
assert_equal exception_pattern, exception_whitelist[0][0]
|
42
42
|
begin
|
43
43
|
ExceptionHandling.log_error("This is a test error")
|
44
|
-
rescue
|
44
|
+
rescue StandardError
|
45
45
|
flunk # Shouldn't raise an error in this case
|
46
46
|
end
|
47
47
|
end
|
@@ -53,7 +53,7 @@ module ExceptionHandling
|
|
53
53
|
assert_equal exception_pattern, exception_whitelist[0][0]
|
54
54
|
begin
|
55
55
|
ExceptionHandling.log_error("This is a test error")
|
56
|
-
rescue
|
56
|
+
rescue StandardError
|
57
57
|
flunk # Shouldn't raise an error in this case
|
58
58
|
end
|
59
59
|
end
|
@@ -79,5 +79,28 @@ module ExceptionHandling
|
|
79
79
|
end
|
80
80
|
end
|
81
81
|
|
82
|
+
context "teardown_log_error_stub" do
|
83
|
+
should "support MiniTest framework for adding a failure" do
|
84
|
+
expects_exception(/foo/)
|
85
|
+
|
86
|
+
mock(self).is_mini_test?.returns { true }
|
87
|
+
|
88
|
+
mock(self).flunk("log_error expected 1 times with pattern: 'foo' found 0")
|
89
|
+
teardown_log_error_stub
|
90
|
+
|
91
|
+
self.exception_whitelist = nil
|
92
|
+
end
|
93
|
+
|
94
|
+
should "support Test::Unit framework for adding a failure" do
|
95
|
+
expects_exception(/foo/)
|
96
|
+
|
97
|
+
mock(self).is_mini_test?.returns { false }
|
98
|
+
|
99
|
+
mock(self).add_failure("log_error expected 1 times with pattern: 'foo' found 0")
|
100
|
+
teardown_log_error_stub
|
101
|
+
|
102
|
+
self.exception_whitelist = nil
|
103
|
+
end
|
104
|
+
end
|
82
105
|
end
|
83
|
-
end
|
106
|
+
end
|
@@ -3,7 +3,7 @@ require File.expand_path('../../../test_helper', __FILE__)
|
|
3
3
|
module ExceptionHandling
|
4
4
|
class MailerTest < ActionMailer::TestCase
|
5
5
|
|
6
|
-
include
|
6
|
+
include ::Rails::Dom::Testing::Assertions::SelectorAssertions
|
7
7
|
tests ExceptionHandling::Mailer
|
8
8
|
|
9
9
|
def dont_stub_log_error
|
@@ -20,15 +20,15 @@ module ExceptionHandling
|
|
20
20
|
|
21
21
|
should "deliver" do
|
22
22
|
#ActionMailer::Base.delivery_method = :smtp
|
23
|
-
result = ExceptionHandling::Mailer.exception_notification({ :error => "Test Error."}).
|
24
|
-
assert_match
|
23
|
+
result = ExceptionHandling::Mailer.exception_notification({ :error => "Test Error."}).deliver_now
|
24
|
+
assert_match(/Test Error./, result.body.to_s)
|
25
25
|
assert_equal_with_diff ['test_exception@invoca.com'], result.to
|
26
26
|
assert_emails 1
|
27
27
|
end
|
28
28
|
|
29
29
|
context "log_parser_exception_notification" do
|
30
30
|
should "send with string" do
|
31
|
-
result = ExceptionHandling::Mailer.log_parser_exception_notification("This is my fake error", "My Fake Subj").
|
31
|
+
result = ExceptionHandling::Mailer.log_parser_exception_notification("This is my fake error", "My Fake Subj").deliver_now
|
32
32
|
assert_equal "Test exception: My Fake Subj: This is my fake error", result.subject
|
33
33
|
assert_match(/This is my fake error/, result.body.to_s)
|
34
34
|
assert_emails 1
|
@@ -36,24 +36,31 @@ module ExceptionHandling
|
|
36
36
|
end
|
37
37
|
|
38
38
|
context "escalation_notification" do
|
39
|
+
setup do
|
40
|
+
def document_root_element
|
41
|
+
@body_html.root
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
39
45
|
should "send all the information" do
|
40
46
|
ExceptionHandling.email_environment = 'Staging Full'
|
41
47
|
ExceptionHandling.server_name = 'test-fe3'
|
42
48
|
|
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 ).
|
49
|
+
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_now
|
44
50
|
|
45
51
|
assert_emails 1
|
46
52
|
result = ActionMailer::Base.deliveries.last
|
47
|
-
body_html = HTML
|
53
|
+
@body_html = Nokogiri::HTML(result.body.to_s)
|
48
54
|
assert_equal_with_diff ['test_escalation@invoca.com'], result.to
|
49
55
|
assert_equal ["Test Escalation Mailer <null_escalation@invoca.com>"], result[:from].formatted
|
50
56
|
assert_equal "Staging Full Escalation: Your Favorite <b>Feature<b> Failed", result.subject
|
51
|
-
assert_select
|
52
|
-
|
57
|
+
assert_select "title", "Exception Escalation"
|
58
|
+
assert_select "html" do
|
53
59
|
assert_select "body br", { :count => 4 }, result.body.to_s # plus 1 for the multiline summary
|
54
|
-
assert_select "body h3", "Your Favorite
|
60
|
+
assert_select "body h3", "Your Favorite <b>Feature<b> Failed", result.body.to_s
|
55
61
|
assert_select "body", /1234567/
|
56
|
-
assert_select "body", /It failed because of an error
|
62
|
+
assert_select "body", /It failed because of an error/
|
63
|
+
assert_select "body", /\n <i>More Info<i>/
|
57
64
|
assert_select "body", /test-fe3/
|
58
65
|
#assert_select "body", /#{Web::Application::GIT_REVISION}/
|
59
66
|
end
|
@@ -61,19 +68,37 @@ module ExceptionHandling
|
|
61
68
|
|
62
69
|
should "use defaults for missing fields" do
|
63
70
|
result = ExceptionHandling::Mailer.escalation_notification("Your Favorite Feature Failed", :error_string => "It failed because of an error\n More Info")
|
64
|
-
body_html = HTML
|
71
|
+
@body_html = Nokogiri::HTML(result.body.to_s)
|
65
72
|
|
66
73
|
assert_equal_with_diff ['test_escalation@invoca.com'], result.to
|
67
74
|
assert_equal ["null_escalation@invoca.com"], result.from
|
68
75
|
assert_equal 'Test Escalation: Your Favorite Feature Failed', result.subject
|
69
|
-
assert_select
|
76
|
+
assert_select "html" do
|
70
77
|
assert_select "body i", true, result.body.to_s do |is|
|
71
|
-
assert_select is
|
78
|
+
assert_select is, "i", 'no error #'
|
72
79
|
end
|
73
80
|
end
|
74
81
|
end
|
82
|
+
|
83
|
+
context "ExceptionHandling.escalate_to_production_support" do
|
84
|
+
setup do
|
85
|
+
Time.now_override = Time.parse('1986-5-21 4:17 am UTC')
|
86
|
+
end
|
87
|
+
|
88
|
+
should "notify production support" do
|
89
|
+
subject = "Runtime Error found!"
|
90
|
+
exception = RuntimeError.new("Test")
|
91
|
+
recipients = ["prodsupport@example.com"]
|
92
|
+
|
93
|
+
ExceptionHandling.production_support_recipients = recipients
|
94
|
+
ExceptionHandling.last_exception_timestamp = Time.now.to_i
|
95
|
+
|
96
|
+
mock(ExceptionHandling).escalate_custom(subject, exception, Time.now.to_i, recipients)
|
97
|
+
ExceptionHandling.escalate_to_production_support(exception, subject)
|
98
|
+
end
|
99
|
+
end
|
75
100
|
end
|
76
101
|
end
|
77
102
|
|
78
103
|
end
|
79
|
-
end
|
104
|
+
end
|
@@ -15,21 +15,18 @@ module ExceptionHandling
|
|
15
15
|
ExceptionHandling.stub_handler = nil
|
16
16
|
end
|
17
17
|
|
18
|
-
teardown do
|
19
|
-
Time.now_override = nil
|
20
|
-
end
|
21
|
-
|
22
18
|
should "set the around filter" do
|
23
19
|
assert_equal :set_current_controller, Testing::ControllerStub.around_filter_method
|
24
20
|
assert_nil ExceptionHandling.current_controller
|
25
|
-
@controller.simulate_around_filter
|
21
|
+
@controller.simulate_around_filter do
|
26
22
|
assert_equal @controller, ExceptionHandling.current_controller
|
27
23
|
end
|
28
24
|
assert_nil ExceptionHandling.current_controller
|
29
25
|
end
|
30
26
|
|
31
27
|
should "use the current_controller when available" do
|
32
|
-
mock(
|
28
|
+
mock(Honeybadger).notify(anything)
|
29
|
+
mock(ExceptionHandling.logger).fatal(/blah/, anything)
|
33
30
|
@controller.simulate_around_filter do
|
34
31
|
ExceptionHandling.log_error( ArgumentError.new("blah") )
|
35
32
|
mail = ActionMailer::Base.deliveries.last
|
@@ -39,21 +36,46 @@ module ExceptionHandling
|
|
39
36
|
end
|
40
37
|
|
41
38
|
should "report long running controller action" do
|
42
|
-
|
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
|
39
|
+
assert_equal 2, @controller.send(:long_controller_action_timeout)
|
51
40
|
mock(ExceptionHandling).log_error(/Long controller action detected in #{@controller.class.name.split("::").last}::test_action/, anything, anything)
|
52
|
-
@controller.simulate_around_filter
|
53
|
-
|
41
|
+
@controller.simulate_around_filter do
|
42
|
+
sleep(3)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
should "not report long running controller actions if it is less than the timeout" do
|
47
|
+
assert_equal 2, @controller.send(:long_controller_action_timeout)
|
48
|
+
stub(ExceptionHandling).log_error { flunk "Should not timeout" }
|
49
|
+
@controller.simulate_around_filter do
|
50
|
+
sleep(1)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
should "default long running controller action(300/30 for test/prod)" do
|
55
|
+
class DummyController
|
56
|
+
include ExceptionHandling::Methods
|
57
|
+
end
|
58
|
+
|
59
|
+
controller = DummyController.new
|
60
|
+
|
61
|
+
Rails.env = 'production'
|
62
|
+
assert_equal 30, controller.send(:long_controller_action_timeout)
|
63
|
+
|
64
|
+
Rails.env = 'test'
|
65
|
+
assert_equal 300, controller.send(:long_controller_action_timeout)
|
66
|
+
end
|
67
|
+
|
68
|
+
context "#log_warning" do
|
69
|
+
should "be available to the controller" do
|
70
|
+
assert_equal true, @controller.methods.include?(:log_warning)
|
71
|
+
end
|
72
|
+
|
73
|
+
should "call ExceptionHandling#log_warning" do
|
74
|
+
mock(ExceptionHandling).log_warning("Hi mom")
|
75
|
+
@controller.send(:log_warning, "Hi mom")
|
54
76
|
end
|
55
77
|
end
|
56
78
|
end
|
57
79
|
|
58
80
|
end
|
59
|
-
end
|
81
|
+
end
|
@@ -1,11 +1,46 @@
|
|
1
1
|
require File.expand_path('../../test_helper', __FILE__)
|
2
|
+
require_test_helper 'controller_helpers'
|
3
|
+
require_test_helper 'exception_helpers'
|
2
4
|
|
3
5
|
class ExceptionHandlingTest < ActiveSupport::TestCase
|
6
|
+
include ControllerHelpers
|
7
|
+
include ExceptionHelpers
|
4
8
|
|
5
9
|
def dont_stub_log_error
|
6
10
|
true
|
7
11
|
end
|
8
12
|
|
13
|
+
def append_organization_info_config(data)
|
14
|
+
data[:user_details] = {}
|
15
|
+
data[:user_details][:username] = "CaryP"
|
16
|
+
data[:user_details][:organization] = "Invoca Engineering Dept."
|
17
|
+
rescue StandardError
|
18
|
+
# don't let these out!
|
19
|
+
end
|
20
|
+
|
21
|
+
def custom_data_callback_returns_nil_message_exception(data)
|
22
|
+
raise_exception_with_nil_message
|
23
|
+
end
|
24
|
+
|
25
|
+
def log_error_callback(data, ex, treat_like_warning, honeybadger_status)
|
26
|
+
@fail_count += 1
|
27
|
+
end
|
28
|
+
|
29
|
+
def log_error_callback_config(data, ex, treat_like_warning, honeybadger_status)
|
30
|
+
@callback_data = data
|
31
|
+
@treat_like_warning = treat_like_warning
|
32
|
+
@fail_count += 1
|
33
|
+
@honeybadger_status = honeybadger_status
|
34
|
+
end
|
35
|
+
|
36
|
+
def log_error_callback_with_failure(data, ex)
|
37
|
+
raise "this should be rescued"
|
38
|
+
end
|
39
|
+
|
40
|
+
def log_error_callback_returns_nil_message_exception(data, ex)
|
41
|
+
raise_exception_with_nil_message
|
42
|
+
end
|
43
|
+
|
9
44
|
module EventMachineStub
|
10
45
|
class << self
|
11
46
|
attr_accessor :block
|
@@ -16,6 +51,25 @@ class ExceptionHandlingTest < ActiveSupport::TestCase
|
|
16
51
|
end
|
17
52
|
end
|
18
53
|
|
54
|
+
class DNSResolvStub
|
55
|
+
class << self
|
56
|
+
attr_accessor :callback_block
|
57
|
+
attr_accessor :errback_block
|
58
|
+
|
59
|
+
def resolve(hostname)
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
def callback(&block)
|
64
|
+
@callback_block = block
|
65
|
+
end
|
66
|
+
|
67
|
+
def errback(&block)
|
68
|
+
@errback_block = block
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
19
73
|
class SmtpClientStub
|
20
74
|
class << self
|
21
75
|
attr_reader :block
|
@@ -47,709 +101,1063 @@ class ExceptionHandlingTest < ActiveSupport::TestCase
|
|
47
101
|
class SmtpClientErrbackStub < SmtpClientStub
|
48
102
|
end
|
49
103
|
|
50
|
-
context "
|
51
|
-
|
52
|
-
|
53
|
-
data[:user_details] = {}
|
54
|
-
data[:user_details][:username] = "CaryP"
|
55
|
-
data[:user_details][:organization] = "Invoca Engineering Dept."
|
56
|
-
rescue Exception => e
|
57
|
-
# don't let these out!
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def log_error_callback_config(data, ex)
|
62
|
-
@callback_data = data
|
63
|
-
@fail_count += 1
|
104
|
+
context "with honeybadger notify stubbed" do
|
105
|
+
setup do
|
106
|
+
stub(Honeybadger).notify(anything)
|
64
107
|
end
|
65
108
|
|
66
|
-
|
67
|
-
|
68
|
-
|
109
|
+
context "#log_error" do
|
110
|
+
setup do
|
111
|
+
ExceptionHandling.mailer_send_enabled = true
|
112
|
+
end
|
69
113
|
|
70
|
-
|
71
|
-
|
72
|
-
|
114
|
+
should "take in additional key word args as logging context and pass them to the logger" do
|
115
|
+
ExceptionHandling.log_error('This is an Error', 'This is the prefix context', service_name: 'exception_handling')
|
116
|
+
assert_match(/This is an Error/, logged_excluding_reload_filter.last[:message])
|
117
|
+
assert_not_empty logged_excluding_reload_filter.last[:context]
|
118
|
+
assert_equal logged_excluding_reload_filter.last[:context], { service_name: 'exception_handling' }
|
119
|
+
end
|
73
120
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
121
|
+
should "log the info and not raise another exception when sending email fails" do
|
122
|
+
9.times { ExceptionHandling.log_error('SomeError', 'Error Context') }
|
123
|
+
mock(ExceptionHandling::Mailer).exception_notification(anything, anything, anything) { raise 'An Error' }
|
124
|
+
mock(ExceptionHandling.logger) do |logger|
|
125
|
+
logger.info(/ExceptionHandling.log_error_email rescued exception while logging StandardError: SomeError/, anything)
|
126
|
+
end
|
127
|
+
stub($stderr).puts
|
128
|
+
ExceptionHandling.log_error('SomeError', 'Error Context')
|
129
|
+
end
|
79
130
|
end
|
80
131
|
|
81
|
-
|
82
|
-
|
83
|
-
ExceptionHandling.
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
ExceptionHandling.post_log_error_hook = nil
|
132
|
+
context "#log_warning" do
|
133
|
+
should "have empty array as a backtrace" do
|
134
|
+
mock(ExceptionHandling).log_error(is_a(ExceptionHandling::Warning), anything) do |error|
|
135
|
+
assert_equal [], error.backtrace
|
136
|
+
end
|
137
|
+
ExceptionHandling.log_warning('Now with empty array as a backtrace!')
|
88
138
|
end
|
89
139
|
|
90
|
-
|
91
|
-
|
140
|
+
should "take in additional key word args as logging context and pass them to the logger" do
|
141
|
+
ExceptionHandling.log_warning('This is a Warning', service_name: 'exception_handling')
|
142
|
+
assert_match(/This is a Warning/, logged_excluding_reload_filter.last[:message])
|
143
|
+
assert_not_empty logged_excluding_reload_filter.last[:context]
|
144
|
+
assert_equal logged_excluding_reload_filter.last[:context], { service_name: 'exception_handling' }
|
145
|
+
end
|
92
146
|
end
|
93
147
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
148
|
+
context "#log_info" do
|
149
|
+
should "take in additional key word args as logging context and pass them to the logger" do
|
150
|
+
ExceptionHandling.log_warning('This is an Info', service_name: 'exception_handling')
|
151
|
+
assert_match(/This is an Info/, logged_excluding_reload_filter.last[:message])
|
152
|
+
assert_not_empty logged_excluding_reload_filter.last[:context]
|
153
|
+
assert_equal logged_excluding_reload_filter.last[:context], { service_name: 'exception_handling' }
|
154
|
+
end
|
99
155
|
end
|
100
156
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
157
|
+
context "#log_debug" do
|
158
|
+
should "take in additional key word args as logging context and pass them to the logger" do
|
159
|
+
ExceptionHandling.log_warning('This is a Debug', service_name: 'exception_handling')
|
160
|
+
assert_match(/This is a Debug/, logged_excluding_reload_filter.last[:message])
|
161
|
+
assert_not_empty logged_excluding_reload_filter.last[:context]
|
162
|
+
assert_equal logged_excluding_reload_filter.last[:context], { service_name: 'exception_handling' }
|
163
|
+
end
|
107
164
|
end
|
108
165
|
|
109
|
-
context "
|
110
|
-
should "
|
111
|
-
|
112
|
-
ExceptionHandling.ensure_safe { raise
|
113
|
-
|
114
|
-
|
115
|
-
should "log an exception with call stack if an ActionView template exception is raised." do
|
116
|
-
mock(ExceptionHandling.logger).fatal(/\(Error:\d+\) ActionView::Template::Error \(blah\):\n <no backtrace>/)
|
117
|
-
ExceptionHandling.ensure_safe { raise ActionView::TemplateError.new({}, {}, ArgumentError.new("blah")) }
|
166
|
+
context "configuration" do
|
167
|
+
should "support a custom_data_hook" do
|
168
|
+
ExceptionHandling.custom_data_hook = method(:append_organization_info_config)
|
169
|
+
ExceptionHandling.ensure_safe("mooo") { raise "Some BS" }
|
170
|
+
assert_match(/Invoca Engineering Dept./, ActionMailer::Base.deliveries[-1].body.to_s)
|
171
|
+
ExceptionHandling.custom_data_hook = nil
|
118
172
|
end
|
119
173
|
|
120
|
-
should "
|
121
|
-
|
122
|
-
|
123
|
-
|
174
|
+
should "support a log_error hook, and pass exception data, treat like warning, and logged_to_honeybadger to it" do
|
175
|
+
begin
|
176
|
+
@fail_count = 0
|
177
|
+
@honeybadger_status = nil
|
178
|
+
ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
|
179
|
+
mock(Honeybadger).notify.with_any_args { '06220c5a-b471-41e5-baeb-de247da45a56' }
|
180
|
+
ExceptionHandling.ensure_safe("mooo") { raise "Some BS" }
|
181
|
+
assert_equal 1, @fail_count
|
182
|
+
assert_equal false, @treat_like_warning
|
183
|
+
assert_equal :success, @honeybadger_status
|
184
|
+
ensure
|
185
|
+
ExceptionHandling.post_log_error_hook = nil
|
186
|
+
end
|
124
187
|
|
125
|
-
|
126
|
-
|
127
|
-
b = ExceptionHandling.ensure_safe { 5 }
|
128
|
-
assert_equal 5, b
|
188
|
+
assert_equal "this is used by a test", @callback_data["notes"]
|
189
|
+
assert_match(/this is used by a test/, ActionMailer::Base.deliveries[-1].body.to_s)
|
129
190
|
end
|
130
191
|
|
131
|
-
should "
|
132
|
-
|
133
|
-
|
134
|
-
|
192
|
+
should "plumb treat like warning and logged_to_honeybadger to log error hook" do
|
193
|
+
begin
|
194
|
+
@fail_count = 0
|
195
|
+
@honeybadger_status = nil
|
196
|
+
ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
|
197
|
+
ExceptionHandling.log_error(StandardError.new("Some BS"), "mooo", treat_like_warning: true)
|
198
|
+
assert_equal 1, @fail_count
|
199
|
+
assert_equal true, @treat_like_warning
|
200
|
+
assert_equal :skipped, @honeybadger_status
|
201
|
+
ensure
|
202
|
+
ExceptionHandling.post_log_error_hook = nil
|
203
|
+
end
|
135
204
|
end
|
136
205
|
|
137
|
-
should "
|
138
|
-
|
139
|
-
|
140
|
-
|
206
|
+
should "support rescue exceptions from a log_error hook" do
|
207
|
+
ExceptionHandling.post_log_error_hook = method(:log_error_callback_with_failure)
|
208
|
+
log_info_messages = []
|
209
|
+
stub(ExceptionHandling.logger).info.with_any_args do |message, _|
|
210
|
+
log_info_messages << message
|
211
|
+
end
|
212
|
+
assert_nothing_raised { ExceptionHandling.ensure_safe("mooo") { raise "Some BS" } }
|
213
|
+
assert log_info_messages.find { |message| message =~ /Unable to execute custom log_error callback/ }
|
214
|
+
ExceptionHandling.post_log_error_hook = nil
|
141
215
|
end
|
142
216
|
|
143
|
-
should "
|
144
|
-
|
145
|
-
|
146
|
-
|
217
|
+
should "handle nil message exceptions resulting from the log_error hook" do
|
218
|
+
ExceptionHandling.post_log_error_hook = method(:log_error_callback_returns_nil_message_exception)
|
219
|
+
log_info_messages = []
|
220
|
+
stub(ExceptionHandling.logger).info.with_any_args do |message, _|
|
221
|
+
log_info_messages << message
|
222
|
+
end
|
223
|
+
assert_nothing_raised { ExceptionHandling.ensure_safe("mooo") { raise "Some BS" } }
|
224
|
+
assert log_info_messages.find { |message| message =~ /Unable to execute custom log_error callback/ }
|
225
|
+
ExceptionHandling.post_log_error_hook = nil
|
226
|
+
end
|
147
227
|
|
148
|
-
|
149
|
-
|
228
|
+
should "handle nil message exceptions resulting from the custom data hook" do
|
229
|
+
ExceptionHandling.custom_data_hook = method(:custom_data_callback_returns_nil_message_exception)
|
230
|
+
log_info_messages = []
|
231
|
+
stub(ExceptionHandling.logger).info.with_any_args do |message, _|
|
232
|
+
log_info_messages << message
|
233
|
+
end
|
234
|
+
assert_nothing_raised { ExceptionHandling.ensure_safe("mooo") { raise "Some BS" } }
|
235
|
+
assert log_info_messages.find { |message| message =~ /Unable to execute custom custom_data_hook callback/ }
|
236
|
+
ExceptionHandling.custom_data_hook = nil
|
150
237
|
end
|
151
238
|
end
|
152
239
|
|
153
|
-
context "
|
154
|
-
|
155
|
-
|
156
|
-
ExceptionHandling.
|
240
|
+
context "Exception Handling" do
|
241
|
+
setup do
|
242
|
+
ActionMailer::Base.deliveries.clear
|
243
|
+
ExceptionHandling.send(:clear_exception_summary)
|
157
244
|
end
|
158
245
|
|
159
|
-
|
160
|
-
mock(ExceptionHandling.logger).fatal.times(0)
|
161
|
-
ExceptionHandling.ensure_completely_safe { ; }
|
162
|
-
end
|
246
|
+
context "default_metric_name" do
|
163
247
|
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
248
|
+
context "when metric_name is present in exception_data" do
|
249
|
+
should "include metric_name in resulting metric name" do
|
250
|
+
exception = StandardError.new('this is an exception')
|
251
|
+
metric = ExceptionHandling.default_metric_name({ 'metric_name' => 'special_metric' }, exception, true)
|
252
|
+
assert_equal 'exception_handling.special_metric', metric
|
253
|
+
end
|
254
|
+
end
|
169
255
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
256
|
+
context "when metric_name is not present in exception_data" do
|
257
|
+
should "return exception_handling.warning when using log warning" do
|
258
|
+
warning = ExceptionHandling::Warning.new('this is a warning')
|
259
|
+
metric = ExceptionHandling.default_metric_name({}, warning, false)
|
260
|
+
assert_equal 'exception_handling.warning', metric
|
261
|
+
end
|
175
262
|
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
263
|
+
should "return exception_handling.exception when using log error" do
|
264
|
+
exception = StandardError.new('this is an exception')
|
265
|
+
metric = ExceptionHandling.default_metric_name({}, exception, false)
|
266
|
+
assert_equal 'exception_handling.exception', metric
|
267
|
+
end
|
181
268
|
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
269
|
+
context "when using log error with treat_like_warning" do
|
270
|
+
should "return exception_handling.unforwarded_exception when exception not present" do
|
271
|
+
metric = ExceptionHandling.default_metric_name({}, nil, true)
|
272
|
+
assert_equal 'exception_handling.unforwarded_exception', metric
|
273
|
+
end
|
274
|
+
|
275
|
+
should "return exception_handling.unforwarded_exception with exception classname when exception is present" do
|
276
|
+
module SomeModule
|
277
|
+
class SomeException < StandardError
|
278
|
+
end
|
279
|
+
end
|
186
280
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
ExceptionHandling.ensure_completely_safe do
|
191
|
-
raise exception.new
|
281
|
+
exception = SomeModule::SomeException.new('this is an exception')
|
282
|
+
metric = ExceptionHandling.default_metric_name({}, exception, true)
|
283
|
+
assert_equal 'exception_handling.unforwarded_exception_SomeException', metric
|
192
284
|
end
|
193
285
|
end
|
194
286
|
end
|
195
287
|
end
|
196
|
-
end
|
197
288
|
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
assert_equal 2, ActionMailer::Base.deliveries.count
|
204
|
-
email = ActionMailer::Base.deliveries.last
|
205
|
-
assert_equal "#{ExceptionHandling.email_environment} Escalation: Favorite Feature", email.subject
|
206
|
-
assert_match 'ArgumentError: blah', email.body.to_s
|
207
|
-
assert_match ExceptionHandling.last_exception_timestamp.to_s, email.body.to_s
|
208
|
-
end
|
289
|
+
context "default_honeybadger_metric_name" do
|
290
|
+
should "return exception_handling.honeybadger.success when status is :success" do
|
291
|
+
metric = ExceptionHandling.default_honeybadger_metric_name(:success)
|
292
|
+
assert_equal 'exception_handling.honeybadger.success', metric
|
293
|
+
end
|
209
294
|
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
assert_equal 0, ActionMailer::Base.deliveries.count
|
215
|
-
end
|
295
|
+
should "return exception_handling.honeybadger.failure when status is :failure" do
|
296
|
+
metric = ExceptionHandling.default_honeybadger_metric_name(:failure)
|
297
|
+
assert_equal 'exception_handling.honeybadger.failure', metric
|
298
|
+
end
|
216
299
|
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
mock(message).deliver { raise RuntimeError.new, "Delivery Error" }
|
300
|
+
should "return exception_handling.honeybadger.skipped when status is :skipped" do
|
301
|
+
metric = ExceptionHandling.default_honeybadger_metric_name(:skipped)
|
302
|
+
assert_equal 'exception_handling.honeybadger.skipped', metric
|
221
303
|
end
|
222
|
-
|
223
|
-
|
224
|
-
|
304
|
+
|
305
|
+
should "return exception_handling.honeybadger.unknown_status when status is not recognized" do
|
306
|
+
metric = ExceptionHandling.default_honeybadger_metric_name(nil)
|
307
|
+
assert_equal 'exception_handling.honeybadger.unknown_status', metric
|
225
308
|
end
|
226
|
-
ExceptionHandling.ensure_escalation("Not Used") { raise ArgumentError.new("first_test_exception") }
|
227
|
-
assert_equal 0, ActionMailer::Base.deliveries.count
|
228
309
|
end
|
229
|
-
end
|
230
310
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
end
|
311
|
+
context "ExceptionHandling.ensure_safe" do
|
312
|
+
should "log an exception with call stack if an exception is raised." do
|
313
|
+
mock(ExceptionHandling.logger).fatal(/\(blah\):\n.*exception_handling_test\.rb/, anything)
|
314
|
+
ExceptionHandling.ensure_safe { raise ArgumentError.new("blah") }
|
315
|
+
end
|
237
316
|
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
end
|
317
|
+
should "log an exception with call stack if an ActionView template exception is raised." do
|
318
|
+
mock(ExceptionHandling.logger).fatal(/\(Error:\d+\) ActionView::Template::Error \(blah\):\n /, anything)
|
319
|
+
ExceptionHandling.ensure_safe { raise ActionView::TemplateError.new({}, ArgumentError.new("blah")) }
|
320
|
+
end
|
243
321
|
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
logger.fatal(/first_test_exception/)
|
248
|
-
logger.fatal(/Failed to send/)
|
322
|
+
should "should not log an exception if an exception is not raised." do
|
323
|
+
dont_allow(ExceptionHandling.logger).fatal
|
324
|
+
ExceptionHandling.ensure_safe { ; }
|
249
325
|
end
|
250
|
-
ExceptionHandling.ensure_alert("Not Used", 'test context') { raise ArgumentError.new("first_test_exception") }
|
251
|
-
end
|
252
|
-
end
|
253
326
|
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
327
|
+
should "return its value if used during an assignment" do
|
328
|
+
dont_allow(ExceptionHandling.logger).fatal
|
329
|
+
b = ExceptionHandling.ensure_safe { 5 }
|
330
|
+
assert_equal 5, b
|
331
|
+
end
|
258
332
|
|
259
|
-
|
260
|
-
|
261
|
-
|
333
|
+
should "return nil if an exception is raised during an assignment" do
|
334
|
+
mock(ExceptionHandling.logger).fatal(/\(blah\):\n.*exception_handling_test\.rb/, anything)
|
335
|
+
b = ExceptionHandling.ensure_safe { raise ArgumentError.new("blah") }
|
336
|
+
assert_nil b
|
337
|
+
end
|
262
338
|
|
339
|
+
should "allow a message to be appended to the error when logged." do
|
340
|
+
mock(ExceptionHandling.logger).fatal(/mooo \(blah\):\n.*exception_handling_test\.rb/, anything)
|
341
|
+
b = ExceptionHandling.ensure_safe("mooo") { raise ArgumentError.new("blah") }
|
342
|
+
assert_nil b
|
343
|
+
end
|
263
344
|
|
264
|
-
|
265
|
-
|
266
|
-
b = ExceptionHandling.ensure_safe("mooo") { raise ArgumentError.new("blah") }
|
267
|
-
assert_nil b
|
345
|
+
should "only rescue StandardError and descendents" do
|
346
|
+
assert_raise(Exception) { ExceptionHandling.ensure_safe("mooo") { raise Exception } }
|
268
347
|
|
269
|
-
|
348
|
+
mock(ExceptionHandling.logger).fatal(/mooo \(blah\):\n.*exception_handling_test\.rb/, anything)
|
270
349
|
|
271
|
-
|
272
|
-
|
350
|
+
b = ExceptionHandling.ensure_safe("mooo") { raise StandardError.new("blah") }
|
351
|
+
assert_nil b
|
352
|
+
end
|
273
353
|
end
|
274
|
-
end
|
275
354
|
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
assert_match(/Exception 2/, ActionMailer::Base.deliveries[-1].subject)
|
282
|
-
end
|
355
|
+
context "ExceptionHandling.ensure_completely_safe" do
|
356
|
+
should "log an exception if an exception is raised." do
|
357
|
+
mock(ExceptionHandling.logger).fatal(/\(blah\):\n.*exception_handling_test\.rb/, anything)
|
358
|
+
ExceptionHandling.ensure_completely_safe { raise ArgumentError.new("blah") }
|
359
|
+
end
|
283
360
|
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
assert_emails 5 do
|
288
|
-
10.times do
|
289
|
-
ExceptionHandling.log_error(exception_1)
|
361
|
+
should "should not log an exception if an exception is not raised." do
|
362
|
+
mock(ExceptionHandling.logger).fatal.times(0)
|
363
|
+
ExceptionHandling.ensure_completely_safe { ; }
|
290
364
|
end
|
291
|
-
end
|
292
|
-
assert_equal 10, @fail_count
|
293
|
-
end
|
294
365
|
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
366
|
+
should "return its value if used during an assignment" do
|
367
|
+
mock(ExceptionHandling.logger).fatal.times(0)
|
368
|
+
b = ExceptionHandling.ensure_completely_safe { 5 }
|
369
|
+
assert_equal 5, b
|
299
370
|
end
|
300
|
-
end
|
301
|
-
assert_emails 1 do
|
302
|
-
ExceptionHandling.log_error(exception_2)
|
303
|
-
end
|
304
|
-
end
|
305
371
|
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
372
|
+
should "return nil if an exception is raised during an assignment" do
|
373
|
+
mock(ExceptionHandling.logger).fatal(/\(blah\):\n.*exception_handling_test\.rb/, anything) { nil }
|
374
|
+
b = ExceptionHandling.ensure_completely_safe { raise ArgumentError.new("blah") }
|
375
|
+
assert_nil b
|
376
|
+
end
|
377
|
+
|
378
|
+
should "allow a message to be appended to the error when logged." do
|
379
|
+
mock(ExceptionHandling.logger).fatal(/mooo \(blah\):\n.*exception_handling_test\.rb/, anything)
|
380
|
+
b = ExceptionHandling.ensure_completely_safe("mooo") { raise ArgumentError.new("blah") }
|
381
|
+
assert_nil b
|
382
|
+
end
|
383
|
+
|
384
|
+
should "rescue any instance or child of Exception" do
|
385
|
+
mock(ExceptionHandling.logger).fatal(/\(blah\):\n.*exception_handling_test\.rb/, anything)
|
386
|
+
ExceptionHandling::ensure_completely_safe { raise Exception.new("blah") }
|
387
|
+
end
|
388
|
+
|
389
|
+
should "not rescue the special exceptions that Ruby uses" do
|
390
|
+
[SystemExit, SystemStackError, NoMemoryError, SecurityError].each do |exception|
|
391
|
+
assert_raise exception do
|
392
|
+
ExceptionHandling.ensure_completely_safe do
|
393
|
+
raise exception.new
|
394
|
+
end
|
395
|
+
end
|
396
|
+
end
|
310
397
|
end
|
311
398
|
end
|
312
|
-
|
313
|
-
|
314
|
-
|
399
|
+
|
400
|
+
context "ExceptionHandling.ensure_escalation" do
|
401
|
+
should "log the exception as usual and send the proper email" do
|
402
|
+
assert_equal 0, ActionMailer::Base.deliveries.count
|
403
|
+
mock(ExceptionHandling.logger).fatal(/\(blah\):\n.*exception_handling_test\.rb/, anything)
|
404
|
+
ExceptionHandling.ensure_escalation( "Favorite Feature") { raise ArgumentError.new("blah") }
|
405
|
+
assert_equal 2, ActionMailer::Base.deliveries.count
|
406
|
+
email = ActionMailer::Base.deliveries.last
|
407
|
+
assert_equal "#{ExceptionHandling.email_environment} Escalation: Favorite Feature", email.subject
|
408
|
+
assert_match 'ArgumentError: blah', email.body.to_s
|
409
|
+
assert_match ExceptionHandling.last_exception_timestamp.to_s, email.body.to_s
|
410
|
+
end
|
411
|
+
|
412
|
+
should "should not escalate if an exception is not raised." do
|
413
|
+
assert_equal 0, ActionMailer::Base.deliveries.count
|
414
|
+
dont_allow(ExceptionHandling.logger).fatal
|
415
|
+
ExceptionHandling.ensure_escalation('Ignored') { ; }
|
416
|
+
assert_equal 0, ActionMailer::Base.deliveries.count
|
417
|
+
end
|
418
|
+
|
419
|
+
should "log if the escalation email can not be sent" do
|
420
|
+
any_instance_of(Mail::Message) do |message|
|
421
|
+
mock(message).deliver
|
422
|
+
mock(message).deliver { raise RuntimeError.new, "Delivery Error" }
|
423
|
+
end
|
424
|
+
mock(ExceptionHandling.logger) do |logger|
|
425
|
+
logger.fatal(/first_test_exception/, anything)
|
426
|
+
logger.fatal(/safe_email_deliver .*Delivery Error/, anything)
|
427
|
+
end
|
428
|
+
ExceptionHandling.ensure_escalation("Not Used") { raise ArgumentError.new("first_test_exception") }
|
429
|
+
assert_equal 0, ActionMailer::Base.deliveries.count
|
430
|
+
end
|
315
431
|
end
|
316
|
-
assert_match(/\[5 SUMMARIZED\]/, ActionMailer::Base.deliveries.last.subject)
|
317
|
-
assert_match(/This exception occurred 5 times since/, ActionMailer::Base.deliveries.last.body.to_s)
|
318
432
|
|
319
|
-
|
320
|
-
|
321
|
-
ExceptionHandling.
|
433
|
+
context "ExceptionHandling.ensure_alert" do
|
434
|
+
should "log the exception as usual and fire a sensu event" do
|
435
|
+
mock(ExceptionHandling::Sensu).generate_event("Favorite Feature", "test context\nblah")
|
436
|
+
mock(ExceptionHandling.logger).fatal(/\(blah\):\n.*exception_handling_test\.rb/, anything)
|
437
|
+
ExceptionHandling.ensure_alert('Favorite Feature', 'test context') { raise ArgumentError.new("blah") }
|
438
|
+
end
|
439
|
+
|
440
|
+
should "should not send sensu event if an exception is not raised." do
|
441
|
+
dont_allow(ExceptionHandling.logger).fatal
|
442
|
+
dont_allow(ExceptionHandling::Sensu).generate_event
|
443
|
+
ExceptionHandling.ensure_alert('Ignored', 'test context') { ; }
|
444
|
+
end
|
445
|
+
|
446
|
+
should "log if the sensu event could not be sent" do
|
447
|
+
mock(ExceptionHandling::Sensu).send_event(anything) { raise "Failed to send" }
|
448
|
+
mock(ExceptionHandling.logger) do |logger|
|
449
|
+
logger.fatal(/first_test_exception/, anything)
|
450
|
+
logger.fatal(/Failed to send/, anything)
|
451
|
+
end
|
452
|
+
ExceptionHandling.ensure_alert("Not Used", 'test context') { raise ArgumentError.new("first_test_exception") }
|
453
|
+
end
|
454
|
+
|
455
|
+
should "log if the exception message is nil" do
|
456
|
+
mock(ExceptionHandling::Sensu).generate_event("some alert", "test context\n")
|
457
|
+
ExceptionHandling.ensure_alert('some alert', 'test context') { raise_exception_with_nil_message }
|
322
458
|
end
|
323
459
|
end
|
324
460
|
|
325
|
-
|
461
|
+
context "ExceptionHandling.escalate_to_production_support" do
|
462
|
+
should "notify production support" do
|
463
|
+
subject = "Runtime Error found!"
|
464
|
+
exception = RuntimeError.new("Test")
|
465
|
+
recipients = ["prodsupport@example.com"]
|
326
466
|
|
327
|
-
|
328
|
-
|
329
|
-
ExceptionHandling.
|
467
|
+
mock(ExceptionHandling).production_support_recipients { recipients }.times(2)
|
468
|
+
mock(ExceptionHandling).escalate_custom(subject, exception, ExceptionHandling.last_exception_timestamp, recipients)
|
469
|
+
ExceptionHandling.escalate_to_production_support(exception, subject)
|
330
470
|
end
|
331
471
|
end
|
332
|
-
assert_match(/\[7 SUMMARIZED\]/, ActionMailer::Base.deliveries[-3].subject)
|
333
|
-
assert_match(/This exception occurred 7 times since/, ActionMailer::Base.deliveries[-3].body.to_s)
|
334
|
-
end
|
335
472
|
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
473
|
+
context "exception timestamp" do
|
474
|
+
setup do
|
475
|
+
Time.now_override = Time.parse( '1986-5-21 4:17 am UTC' )
|
476
|
+
end
|
477
|
+
|
478
|
+
should "include the timestamp when the exception is logged" do
|
479
|
+
mock(ExceptionHandling.logger).fatal(/\(Error:517033020\) ArgumentError mooo \(blah\):\n.*exception_handling_test\.rb/, anything)
|
480
|
+
b = ExceptionHandling.ensure_safe("mooo") { raise ArgumentError.new("blah") }
|
481
|
+
assert_nil b
|
482
|
+
|
483
|
+
assert_equal 517033020, ExceptionHandling.last_exception_timestamp
|
484
|
+
|
485
|
+
assert_emails 1
|
486
|
+
assert_match(/517033020/, ActionMailer::Base.deliveries[-1].body.to_s)
|
340
487
|
end
|
341
488
|
end
|
342
489
|
|
343
|
-
|
490
|
+
should "send just one copy of exceptions that don't repeat" do
|
491
|
+
ExceptionHandling.log_error(exception_1)
|
344
492
|
ExceptionHandling.log_error(exception_2)
|
493
|
+
assert_emails 2
|
494
|
+
assert_match(/Exception 1/, ActionMailer::Base.deliveries[-2].subject)
|
495
|
+
assert_match(/Exception 2/, ActionMailer::Base.deliveries[-1].subject)
|
496
|
+
end
|
497
|
+
|
498
|
+
should "not send emails if exception is a warning" do
|
499
|
+
ExceptionHandling.log_error(ExceptionHandling::Warning.new("Don't send email"))
|
500
|
+
assert_emails 0
|
345
501
|
end
|
346
502
|
|
347
|
-
|
348
|
-
|
503
|
+
should "not send emails when log_warning is called" do
|
504
|
+
ExceptionHandling.log_warning("Don't send email")
|
505
|
+
assert_emails 0
|
506
|
+
end
|
349
507
|
|
350
|
-
|
351
|
-
|
352
|
-
|
508
|
+
should "log the error if the exception message is nil" do
|
509
|
+
ExceptionHandling.log_error(exception_with_nil_message)
|
510
|
+
assert_emails(1)
|
511
|
+
assert_match(/RuntimeError/, ActionMailer::Base.deliveries.last.subject)
|
512
|
+
end
|
513
|
+
|
514
|
+
should "log the error if the exception message is nil and the exception context is a hash" do
|
515
|
+
ExceptionHandling.log_error(exception_with_nil_message, "SERVER_NAME" => "exceptional.com")
|
516
|
+
assert_emails(1)
|
517
|
+
assert_match(/RuntimeError/, ActionMailer::Base.deliveries.last.subject)
|
518
|
+
end
|
519
|
+
|
520
|
+
should "only send 5 of a repeated error, but call post hook for every exception" do
|
521
|
+
@fail_count = 0
|
522
|
+
ExceptionHandling.post_log_error_hook = method(:log_error_callback)
|
523
|
+
assert_emails 5 do
|
524
|
+
10.times do
|
525
|
+
ExceptionHandling.log_error(exception_1)
|
526
|
+
end
|
353
527
|
end
|
528
|
+
assert_equal 10, @fail_count
|
354
529
|
end
|
355
530
|
|
356
|
-
|
357
|
-
|
358
|
-
|
531
|
+
should "only send 5 of a repeated error but don't send summary if 6th is different" do
|
532
|
+
assert_emails 5 do
|
533
|
+
5.times do
|
534
|
+
ExceptionHandling.log_error(exception_1)
|
535
|
+
end
|
536
|
+
end
|
537
|
+
assert_emails 1 do
|
538
|
+
ExceptionHandling.log_error(exception_2)
|
359
539
|
end
|
360
540
|
end
|
361
|
-
end
|
362
541
|
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
542
|
+
should "send the summary when the error is encountered an hour after the first occurrence" do
|
543
|
+
assert_emails 5 do # 5 exceptions, 4 summarized
|
544
|
+
9.times do |t|
|
545
|
+
ExceptionHandling.log_error(exception_1)
|
546
|
+
end
|
547
|
+
end
|
548
|
+
Time.now_override = 2.hours.from_now
|
549
|
+
assert_emails 1 do # 1 summary (4 + 1 = 5) after 2 hours
|
367
550
|
ExceptionHandling.log_error(exception_1)
|
368
551
|
end
|
552
|
+
assert_match(/\[5 SUMMARIZED\]/, ActionMailer::Base.deliveries.last.subject)
|
553
|
+
assert_match(/This exception occurred 5 times since/, ActionMailer::Base.deliveries.last.body.to_s)
|
369
554
|
|
370
|
-
|
371
|
-
|
372
|
-
|
555
|
+
assert_emails 0 do # still summarizing...
|
556
|
+
7.times do
|
557
|
+
ExceptionHandling.log_error(exception_1)
|
558
|
+
end
|
559
|
+
end
|
560
|
+
|
561
|
+
Time.now_override = 3.hours.from_now
|
562
|
+
|
563
|
+
assert_emails 1 + 2 do # 1 summary and 2 new
|
564
|
+
2.times do
|
565
|
+
ExceptionHandling.log_error(exception_2)
|
566
|
+
end
|
373
567
|
end
|
568
|
+
assert_match(/\[7 SUMMARIZED\]/, ActionMailer::Base.deliveries[-3].subject)
|
569
|
+
assert_match(/This exception occurred 7 times since/, ActionMailer::Base.deliveries[-3].body.to_s)
|
374
570
|
end
|
375
571
|
|
376
|
-
|
377
|
-
|
378
|
-
|
572
|
+
should "send the summary if a summary is available, but not sent when another exception comes up" do
|
573
|
+
assert_emails 5 do # 5 to start summarizing
|
574
|
+
6.times do
|
575
|
+
ExceptionHandling.log_error(exception_1)
|
576
|
+
end
|
379
577
|
end
|
380
578
|
|
381
|
-
|
382
|
-
ExceptionHandling.
|
383
|
-
ExceptionHandling.log_error(exception_1)
|
579
|
+
assert_emails 1 + 1 do # 1 summary of previous, 1 from new exception
|
580
|
+
ExceptionHandling.log_error(exception_2)
|
384
581
|
end
|
385
582
|
|
386
|
-
|
387
|
-
|
388
|
-
|
583
|
+
assert_match(/\[1 SUMMARIZED\]/, ActionMailer::Base.deliveries[-2].subject)
|
584
|
+
assert_match(/This exception occurred 1 times since/, ActionMailer::Base.deliveries[-2].body.to_s)
|
585
|
+
|
586
|
+
assert_emails 5 do # 5 to start summarizing
|
587
|
+
10.times do
|
588
|
+
ExceptionHandling.log_error(exception_1)
|
589
|
+
end
|
389
590
|
end
|
390
|
-
end
|
391
|
-
end
|
392
591
|
|
393
|
-
|
394
|
-
|
395
|
-
|
592
|
+
assert_emails 0 do # still summarizing
|
593
|
+
11.times do
|
594
|
+
ExceptionHandling.log_error(exception_1)
|
595
|
+
end
|
596
|
+
end
|
396
597
|
end
|
397
|
-
end
|
398
598
|
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
assert_match(/message from to_s!/, ActionMailer::Base.deliveries.last.body.to_s)
|
405
|
-
end
|
406
|
-
end
|
599
|
+
context "Honeybadger integration" do
|
600
|
+
context "with Honeybadger not defined" do
|
601
|
+
setup do
|
602
|
+
stub(ExceptionHandling).honeybadger? { false }
|
603
|
+
end
|
407
604
|
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
anything)
|
413
|
-
stub($stderr).puts
|
414
|
-
ExceptionHandling.log_error(RuntimeError.new("A runtime error"), "Runtime message")
|
415
|
-
end
|
605
|
+
should "not invoke send_exception_to_honeybadger when log_error is executed" do
|
606
|
+
dont_allow(ExceptionHandling).send_exception_to_honeybadger
|
607
|
+
ExceptionHandling.log_error(exception_1)
|
608
|
+
end
|
416
609
|
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
end
|
610
|
+
should "not invoke send_exception_to_honeybadger when ensure_safe is executed" do
|
611
|
+
dont_allow(ExceptionHandling).send_exception_to_honeybadger
|
612
|
+
ExceptionHandling.ensure_safe { raise exception_1 }
|
613
|
+
end
|
614
|
+
end
|
423
615
|
|
424
|
-
|
425
|
-
|
426
|
-
|
427
|
-
|
428
|
-
|
616
|
+
context "with Honeybadger defined" do
|
617
|
+
should "not send_exception_to_honeybadger when log_warning is executed" do
|
618
|
+
dont_allow(ExceptionHandling).send_exception_to_honeybadger
|
619
|
+
ExceptionHandling.log_warning("This should not go to honeybadger")
|
620
|
+
end
|
429
621
|
|
430
|
-
|
431
|
-
|
432
|
-
|
622
|
+
should "not send_exception_to_honeybadger when log_error is called with a Warning" do
|
623
|
+
dont_allow(ExceptionHandling).send_exception_to_honeybadger
|
624
|
+
ExceptionHandling.log_error(ExceptionHandling::Warning.new("This should not go to honeybadger"))
|
625
|
+
end
|
433
626
|
|
434
|
-
|
435
|
-
|
627
|
+
should "invoke send_exception_to_honeybadger when log_error is executed" do
|
628
|
+
mock.proxy(ExceptionHandling).send_exception_to_honeybadger.with_any_args
|
629
|
+
ExceptionHandling.log_error(exception_1)
|
630
|
+
end
|
436
631
|
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
632
|
+
should "invoke send_exception_to_honeybadger when log_error_rack is executed" do
|
633
|
+
mock.proxy(ExceptionHandling).send_exception_to_honeybadger.with_any_args
|
634
|
+
ExceptionHandling.log_error_rack(exception_1, {}, nil)
|
635
|
+
end
|
441
636
|
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
637
|
+
should "invoke send_exception_to_honeybadger when ensure_safe is executed" do
|
638
|
+
mock.proxy(ExceptionHandling).send_exception_to_honeybadger.with_any_args
|
639
|
+
ExceptionHandling.ensure_safe { raise exception_1 }
|
640
|
+
end
|
446
641
|
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
642
|
+
should "specify error message as an empty string when notifying honeybadger if exception message is nil" do
|
643
|
+
mock(Honeybadger).notify.with_any_args do |args|
|
644
|
+
assert_equal "", args[:error_message]
|
645
|
+
end
|
646
|
+
ExceptionHandling.log_error(exception_with_nil_message)
|
647
|
+
end
|
648
|
+
|
649
|
+
should "send error details and relevant context data to Honeybadger" do
|
650
|
+
Time.now_override = Time.now
|
651
|
+
env = { server: "fe98" }
|
652
|
+
parameters = { advertiser_id: 435 }
|
653
|
+
session = { username: "jsmith" }
|
654
|
+
request_uri = "host/path"
|
655
|
+
controller = create_dummy_controller(env, parameters, session, request_uri)
|
656
|
+
stub(ExceptionHandling).server_name { "invoca_fe98" }
|
657
|
+
|
658
|
+
exception = StandardError.new("Some BS")
|
659
|
+
exception.set_backtrace([
|
660
|
+
"test/unit/exception_handling_test.rb:847:in `exception_1'",
|
661
|
+
"test/unit/exception_handling_test.rb:455:in `block (4 levels) in <class:ExceptionHandlingTest>'"])
|
662
|
+
exception_context = { "SERVER_NAME" => "exceptional.com" }
|
663
|
+
|
664
|
+
honeybadger_data = nil
|
665
|
+
mock(Honeybadger).notify.with_any_args do |data|
|
666
|
+
honeybadger_data = data
|
667
|
+
end
|
668
|
+
ExceptionHandling.log_error(exception, exception_context, controller) do |data|
|
669
|
+
data[:scm_revision] = "5b24eac37aaa91f5784901e9aabcead36fd9df82"
|
670
|
+
data[:user_details] = { username: "jsmith" }
|
671
|
+
data[:event_response] = "Event successfully received"
|
672
|
+
data[:other_section] = "This should not be included in the response"
|
673
|
+
end
|
674
|
+
|
675
|
+
expected_data = {
|
676
|
+
error_class: :"Test BS",
|
677
|
+
error_message: "Some BS",
|
678
|
+
exception: exception,
|
679
|
+
context: {
|
680
|
+
timestamp: Time.now.to_i,
|
681
|
+
error_class: "StandardError",
|
682
|
+
server: "invoca_fe98",
|
683
|
+
exception_context: { "SERVER_NAME" => "exceptional.com" },
|
684
|
+
scm_revision: "5b24eac37aaa91f5784901e9aabcead36fd9df82",
|
685
|
+
notes: "this is used by a test",
|
686
|
+
user_details: { "username" => "jsmith" },
|
687
|
+
request: {
|
688
|
+
"params" => { "advertiser_id" => 435 },
|
689
|
+
"rails_root" => "Rails.root not defined. Is this a test environment?",
|
690
|
+
"url" => "host/path" },
|
691
|
+
session: {
|
692
|
+
"key" => nil,
|
693
|
+
"data" => { "username" => "jsmith" } },
|
694
|
+
environment: {
|
695
|
+
"SERVER_NAME" => "exceptional.com" },
|
696
|
+
backtrace: [
|
697
|
+
"test/unit/exception_handling_test.rb:847:in `exception_1'",
|
698
|
+
"test/unit/exception_handling_test.rb:455:in `block (4 levels) in <class:ExceptionHandlingTest>'"],
|
699
|
+
event_response: "Event successfully received"
|
700
|
+
}
|
701
|
+
}
|
702
|
+
assert_equal_with_diff expected_data, honeybadger_data
|
703
|
+
end
|
451
704
|
|
452
|
-
|
453
|
-
|
454
|
-
|
705
|
+
should "not send notification to honeybadger when exception description has the flag turned off and call log error callback with logged_to_honeybadger set to nil" do
|
706
|
+
begin
|
707
|
+
@fail_count = 0
|
708
|
+
@honeybadger_status = nil
|
709
|
+
ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
|
710
|
+
filter_list = {
|
711
|
+
NoHoneybadger: {
|
712
|
+
error: "suppress Honeybadger notification",
|
713
|
+
send_to_honeybadger: false
|
714
|
+
}
|
715
|
+
}
|
716
|
+
stub(File).mtime { incrementing_mtime }
|
717
|
+
mock(YAML).load_file.with_any_args { ActiveSupport::HashWithIndifferentAccess.new(filter_list) }.at_least(1)
|
718
|
+
|
719
|
+
exception = StandardError.new("suppress Honeybadger notification")
|
720
|
+
mock.proxy(ExceptionHandling).send_exception_to_honeybadger.with_any_args.once
|
721
|
+
dont_allow(Honeybadger).notify
|
722
|
+
ExceptionHandling.log_error(exception)
|
723
|
+
assert_equal :skipped, @honeybadger_status
|
724
|
+
ensure
|
725
|
+
ExceptionHandling.post_log_error_hook = nil
|
726
|
+
end
|
727
|
+
end
|
728
|
+
|
729
|
+
should "call log error callback with logged_to_honeybadger set to false if an error occurs while attempting to notify honeybadger" do
|
730
|
+
begin
|
731
|
+
@fail_count = 0
|
732
|
+
@honeybadger_status = nil
|
733
|
+
ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
|
734
|
+
stub($stderr).puts
|
735
|
+
mock(Honeybadger).notify.with_any_args { raise "Honeybadger Notification Failure" }
|
736
|
+
ExceptionHandling.log_error(exception_1)
|
737
|
+
assert_equal :failure, @honeybadger_status
|
738
|
+
ensure
|
739
|
+
ExceptionHandling.post_log_error_hook = nil
|
740
|
+
end
|
741
|
+
end
|
455
742
|
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
743
|
+
should "call log error callback with logged_to_honeybadger set to false on unsuccessful honeybadger notification" do
|
744
|
+
begin
|
745
|
+
@fail_count = 0
|
746
|
+
@honeybadger_status = nil
|
747
|
+
ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
|
748
|
+
stub($stderr).puts
|
749
|
+
mock(Honeybadger).notify.with_any_args { false }
|
750
|
+
ExceptionHandling.log_error(exception_1)
|
751
|
+
assert_equal :failure, @honeybadger_status
|
752
|
+
ensure
|
753
|
+
ExceptionHandling.post_log_error_hook = nil
|
754
|
+
end
|
755
|
+
end
|
756
|
+
|
757
|
+
should "call log error callback with logged_to_honeybadger set to true on successful honeybadger notification" do
|
758
|
+
begin
|
759
|
+
@fail_count = 0
|
760
|
+
@honeybadger_status = nil
|
761
|
+
ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
|
762
|
+
stub($stderr).puts
|
763
|
+
mock(Honeybadger).notify.with_any_args { '06220c5a-b471-41e5-baeb-de247da45a56' }
|
764
|
+
ExceptionHandling.log_error(exception_1)
|
765
|
+
assert_equal :success, @honeybadger_status
|
766
|
+
ensure
|
767
|
+
ExceptionHandling.post_log_error_hook = nil
|
768
|
+
end
|
769
|
+
end
|
770
|
+
end
|
462
771
|
end
|
463
|
-
assert_emails 0
|
464
772
|
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
:data => "my extra session <no match!> data"
|
470
|
-
}
|
773
|
+
class EventResponse
|
774
|
+
def to_s
|
775
|
+
"message from to_s!"
|
776
|
+
end
|
471
777
|
end
|
472
|
-
assert_emails 1, ActionMailer::Base.deliveries.*.body.*.inspect
|
473
|
-
end
|
474
778
|
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
779
|
+
should "allow sections to have data with just a to_s method" do
|
780
|
+
ExceptionHandling.log_error("This is my RingSwitch example. Log, don't email!") do |data|
|
781
|
+
data.merge!(:event_response => EventResponse.new)
|
782
|
+
end
|
783
|
+
assert_emails 1
|
784
|
+
assert_match(/message from to_s!/, ActionMailer::Base.deliveries.last.body.to_s)
|
785
|
+
end
|
786
|
+
end
|
479
787
|
|
480
|
-
|
481
|
-
stub(
|
482
|
-
|
788
|
+
should "rescue exceptions that happen in log_error" do
|
789
|
+
stub(ExceptionHandling).make_exception { raise ArgumentError.new("Bad argument") }
|
790
|
+
mock(ExceptionHandling).write_exception_to_log(satisfy { |ex| ex.to_s['Bad argument'] },
|
791
|
+
satisfy { |context| context['ExceptionHandlingError: log_error rescued exception while logging Runtime message'] },
|
792
|
+
anything)
|
793
|
+
stub($stderr).puts
|
794
|
+
ExceptionHandling.log_error(RuntimeError.new("A runtime error"), "Runtime message")
|
795
|
+
end
|
483
796
|
|
484
|
-
|
485
|
-
ExceptionHandling.
|
486
|
-
|
797
|
+
should "rescue exceptions that happen when log_error yields" do
|
798
|
+
mock(ExceptionHandling).write_exception_to_log(satisfy { |ex| ex.to_s['Bad argument'] },
|
799
|
+
satisfy { |context| context['Context message'] },
|
800
|
+
anything,
|
801
|
+
anything)
|
802
|
+
ExceptionHandling.log_error(ArgumentError.new("Bad argument"), "Context message") { |data| raise 'Error!!!' }
|
487
803
|
end
|
488
804
|
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
805
|
+
context "Exception Filtering" do
|
806
|
+
setup do
|
807
|
+
filter_list = { :exception1 => { 'error' => "my error message" },
|
808
|
+
:exception2 => { 'error' => "some other message", :session => "misc data" } }
|
809
|
+
stub(YAML).load_file { ActiveSupport::HashWithIndifferentAccess.new(filter_list) }
|
494
810
|
|
495
|
-
|
496
|
-
|
497
|
-
ExceptionHandling.log_error( "some other message" ) do |data|
|
498
|
-
data[:session] = {:some_random_key => "misc data"}
|
811
|
+
# bump modified time up to get the above filter loaded
|
812
|
+
stub(File).mtime { incrementing_mtime }
|
499
813
|
end
|
500
|
-
assert_emails 0, ActionMailer::Base.deliveries.*.body.*.inspect
|
501
|
-
end
|
502
814
|
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
815
|
+
should "handle case where filter list is not found" do
|
816
|
+
stub(YAML).load_file { raise Errno::ENOENT.new("File not found") }
|
817
|
+
|
818
|
+
ActionMailer::Base.deliveries.clear
|
819
|
+
ExceptionHandling.log_error( "My error message is in list" )
|
820
|
+
assert_emails 1
|
507
821
|
end
|
508
|
-
assert_emails 1, ActionMailer::Base.deliveries.*.body.*.inspect
|
509
|
-
mail = ActionMailer::Base.deliveries.last
|
510
|
-
assert_nil mail.body.to_s["RAILS_SECRETS_YML_CONTENTS"], mail.body.to_s # this is not on whitelist
|
511
|
-
assert mail.body.to_s["SERVER_PROTOCOL: HTTP/1.0" ], mail.body.to_s # this is
|
512
|
-
end
|
513
822
|
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
823
|
+
should "log exception and suppress email when exception is on filter list" do
|
824
|
+
ActionMailer::Base.deliveries.clear
|
825
|
+
ExceptionHandling.log_error( "Error message is not in list" )
|
826
|
+
assert_emails 1
|
827
|
+
|
828
|
+
ActionMailer::Base.deliveries.clear
|
829
|
+
ExceptionHandling.log_error( "My error message is in list" )
|
830
|
+
assert_emails 0
|
518
831
|
end
|
519
|
-
assert_emails 1, ActionMailer::Base.deliveries.*.body.*.inspect
|
520
|
-
mail = ActionMailer::Base.deliveries.last
|
521
|
-
assert_nil mail.body.to_s["SERVER_PORT" ], mail.body.to_s # this was default
|
522
|
-
assert mail.body.to_s["SERVER_PROTOCOL: HTTP/1.0"], mail.body.to_s # this was not
|
523
|
-
end
|
524
832
|
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
stub(YAML).load_file { ActiveSupport::HashWithIndifferentAccess.new(filter_list) }
|
529
|
-
stub(File).mtime { incrementing_mtime }
|
833
|
+
should "allow filtering exception on any text in exception data" do
|
834
|
+
filters = { :exception1 => { :session => "data: my extra session data" } }
|
835
|
+
stub(YAML).load_file { ActiveSupport::HashWithIndifferentAccess.new(filters) }
|
530
836
|
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
837
|
+
ActionMailer::Base.deliveries.clear
|
838
|
+
ExceptionHandling.log_error( "No match here" ) do |data|
|
839
|
+
data[:session] = {
|
840
|
+
:key => "@session_id",
|
841
|
+
:data => "my extra session data"
|
842
|
+
}
|
843
|
+
end
|
844
|
+
assert_emails 0
|
845
|
+
|
846
|
+
ActionMailer::Base.deliveries.clear
|
847
|
+
ExceptionHandling.log_error( "No match here" ) do |data|
|
848
|
+
data[:session] = {
|
849
|
+
:key => "@session_id",
|
850
|
+
:data => "my extra session <no match!> data"
|
851
|
+
}
|
852
|
+
end
|
853
|
+
assert_emails 1, ActionMailer::Base.deliveries.*.body.*.inspect
|
854
|
+
end
|
855
|
+
|
856
|
+
should "reload filter list on the next exception if file was modified" do
|
857
|
+
ActionMailer::Base.deliveries.clear
|
858
|
+
ExceptionHandling.log_error( "Error message is not in list" )
|
859
|
+
assert_emails 1
|
860
|
+
|
861
|
+
filter_list = { :exception1 => { 'error' => "Error message is not in list" } }
|
862
|
+
stub(YAML).load_file { ActiveSupport::HashWithIndifferentAccess.new(filter_list) }
|
863
|
+
stub(File).mtime { incrementing_mtime }
|
864
|
+
|
865
|
+
ActionMailer::Base.deliveries.clear
|
866
|
+
ExceptionHandling.log_error( "Error message is not in list" )
|
867
|
+
assert_emails 0, ActionMailer::Base.deliveries.*.body.*.inspect
|
868
|
+
end
|
869
|
+
|
870
|
+
should "not consider filter if both error message and body do not match" do
|
871
|
+
# error message matches, but not full text
|
872
|
+
ActionMailer::Base.deliveries.clear
|
873
|
+
ExceptionHandling.log_error( "some other message" )
|
874
|
+
assert_emails 1, ActionMailer::Base.deliveries.*.body.*.inspect
|
875
|
+
|
876
|
+
# now both match
|
877
|
+
ActionMailer::Base.deliveries.clear
|
878
|
+
ExceptionHandling.log_error( "some other message" ) do |data|
|
879
|
+
data[:session] = {:some_random_key => "misc data"}
|
880
|
+
end
|
881
|
+
assert_emails 0, ActionMailer::Base.deliveries.*.body.*.inspect
|
882
|
+
end
|
535
883
|
|
536
|
-
|
537
|
-
|
538
|
-
ExceptionHandling.log_error(
|
539
|
-
data[:
|
540
|
-
data[:session] = { :key => "DECAFE" }
|
884
|
+
should "skip environment keys not on whitelist" do
|
885
|
+
ActionMailer::Base.deliveries.clear
|
886
|
+
ExceptionHandling.log_error( "some message" ) do |data|
|
887
|
+
data[:environment] = { :SERVER_PROTOCOL => "HTTP/1.0", :RAILS_SECRETS_YML_CONTENTS => 'password: VERY_SECRET_PASSWORD' }
|
541
888
|
end
|
889
|
+
assert_emails 1, ActionMailer::Base.deliveries.*.body.*.inspect
|
890
|
+
mail = ActionMailer::Base.deliveries.last
|
891
|
+
assert_nil mail.body.to_s["RAILS_SECRETS_YML_CONTENTS"], mail.body.to_s # this is not on whitelist
|
892
|
+
assert mail.body.to_s["SERVER_PROTOCOL: HTTP/1.0" ], mail.body.to_s # this is
|
893
|
+
end
|
894
|
+
|
895
|
+
should "omit environment defaults" do
|
896
|
+
ActionMailer::Base.deliveries.clear
|
897
|
+
ExceptionHandling.log_error( "some message" ) do |data|
|
898
|
+
data[:environment] = {:SERVER_PORT => '80', :SERVER_PROTOCOL => "HTTP/1.0"}
|
899
|
+
end
|
900
|
+
assert_emails 1, ActionMailer::Base.deliveries.*.body.*.inspect
|
901
|
+
mail = ActionMailer::Base.deliveries.last
|
902
|
+
assert_nil mail.body.to_s["SERVER_PORT" ], mail.body.to_s # this was default
|
903
|
+
assert mail.body.to_s["SERVER_PROTOCOL: HTTP/1.0"], mail.body.to_s # this was not
|
904
|
+
end
|
905
|
+
|
906
|
+
should "reject the filter file if any contain all empty regexes" do
|
907
|
+
filter_list = { :exception1 => { 'error' => "", :session => "" },
|
908
|
+
:exception2 => { 'error' => "is not in list", :session => "" } }
|
909
|
+
stub(YAML).load_file { ActiveSupport::HashWithIndifferentAccess.new(filter_list) }
|
910
|
+
stub(File).mtime { incrementing_mtime }
|
911
|
+
|
912
|
+
ActionMailer::Base.deliveries.clear
|
913
|
+
ExceptionHandling.log_error( "Error message is not in list" )
|
542
914
|
assert_emails 1, ActionMailer::Base.deliveries.*.inspect
|
543
|
-
|
544
|
-
assert_equal ['exceptions@example.com'], mail.to
|
545
|
-
assert_equal ['server@example.com'].to_s, mail.from.to_s
|
546
|
-
assert_match /Exception 1/, mail.to_s
|
547
|
-
assert_match /key: DECAFE/, mail.to_s
|
548
|
-
assert_match /id: 10993/, mail.to_s
|
549
|
-
end
|
550
|
-
|
551
|
-
EXPECTED_SMTP_HASH =
|
552
|
-
{
|
553
|
-
:host => 'localhost',
|
554
|
-
:domain => 'localhost.localdomain',
|
555
|
-
:from => 'server@example.com',
|
556
|
-
:to => 'exceptions@example.com'
|
557
|
-
}
|
558
|
-
|
559
|
-
[[true, false], [true, true]].each do |em_flag, synchrony_flag|
|
560
|
-
context "eventmachine_safe = #{em_flag} && eventmachine_synchrony = #{synchrony_flag}" do
|
561
|
-
setup do
|
562
|
-
ExceptionHandling.eventmachine_safe = em_flag
|
563
|
-
ExceptionHandling.eventmachine_synchrony = synchrony_flag
|
564
|
-
EventMachineStub.block = nil
|
565
|
-
set_test_const('EventMachine', EventMachineStub)
|
566
|
-
set_test_const('EventMachine::Protocols', Module.new)
|
567
|
-
end
|
915
|
+
end
|
568
916
|
|
569
|
-
|
570
|
-
|
571
|
-
|
917
|
+
should "reload filter file if filename changes" do
|
918
|
+
catalog = ExceptionHandling.exception_catalog
|
919
|
+
ExceptionHandling.filter_list_filename = "./config/other_exception_filters.yml"
|
920
|
+
assert_not_equal catalog, ExceptionHandling.exception_catalog
|
921
|
+
end
|
922
|
+
|
923
|
+
context "Exception Handling Mailer" do
|
924
|
+
should "create email" do
|
925
|
+
ExceptionHandling.log_error(exception_1) do |data|
|
926
|
+
data[:request] = { :params => {:id => 10993}, :url => "www.ringrevenue.com" }
|
927
|
+
data[:session] = { :key => "DECAFE" }
|
572
928
|
end
|
929
|
+
assert_emails 1, ActionMailer::Base.deliveries.*.inspect
|
930
|
+
assert mail = ActionMailer::Base.deliveries.last
|
931
|
+
assert_equal ['exceptions@example.com'], mail.to
|
932
|
+
assert_equal ['server@example.com'].to_s, mail.from.to_s
|
933
|
+
assert_match(/Exception 1/, mail.to_s)
|
934
|
+
assert_match(/key: DECAFE/, mail.to_s)
|
935
|
+
assert_match(/id: 10993/, mail.to_s)
|
936
|
+
end
|
573
937
|
|
574
|
-
|
575
|
-
|
938
|
+
EXPECTED_SMTP_HASH =
|
939
|
+
{
|
940
|
+
:host => '127.0.0.1',
|
941
|
+
:domain => 'localhost.localdomain',
|
942
|
+
:from => 'server@example.com',
|
943
|
+
:to => 'exceptions@example.com'
|
944
|
+
}
|
945
|
+
|
946
|
+
[[true, false], [true, true]].each do |em_flag, synchrony_flag|
|
947
|
+
context "eventmachine_safe = #{em_flag} && eventmachine_synchrony = #{synchrony_flag}" do
|
948
|
+
setup do
|
949
|
+
ExceptionHandling.eventmachine_safe = em_flag
|
950
|
+
ExceptionHandling.eventmachine_synchrony = synchrony_flag
|
951
|
+
EventMachineStub.block = nil
|
952
|
+
set_test_const('EventMachine', EventMachineStub)
|
953
|
+
set_test_const('EventMachine::Protocols', Module.new)
|
954
|
+
set_test_const('EventMachine::DNS', Module.new)
|
955
|
+
set_test_const('EventMachine::DNS::Resolver', DNSResolvStub)
|
956
|
+
end
|
576
957
|
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
581
|
-
assert_equal((synchrony_flag ? :asend : :send), SmtpClientStub.last_method)
|
582
|
-
assert_match(/Exception 1/, SmtpClientStub.send_hash[:content])
|
583
|
-
assert_emails 0, ActionMailer::Base.deliveries.*.to_s
|
584
|
-
end
|
958
|
+
teardown do
|
959
|
+
ExceptionHandling.eventmachine_safe = false
|
960
|
+
ExceptionHandling.eventmachine_synchrony = false
|
961
|
+
end
|
585
962
|
|
586
|
-
|
587
|
-
|
588
|
-
|
589
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
963
|
+
should "schedule EventMachine STMP when EventMachine defined" do
|
964
|
+
set_test_const('EventMachine::Protocols::SmtpClient', SmtpClientStub)
|
965
|
+
|
966
|
+
ExceptionHandling.log_error(exception_1)
|
967
|
+
assert EventMachineStub.block
|
968
|
+
EventMachineStub.block.call
|
969
|
+
assert DNSResolvStub.callback_block
|
970
|
+
DNSResolvStub.callback_block.call ['127.0.0.1']
|
971
|
+
assert_equal_with_diff EXPECTED_SMTP_HASH, (SmtpClientStub.send_hash & EXPECTED_SMTP_HASH.keys).map_hash { |k,v| v.to_s }, SmtpClientStub.send_hash.inspect
|
972
|
+
assert_equal((synchrony_flag ? :asend : :send), SmtpClientStub.last_method)
|
973
|
+
assert_match(/Exception 1/, SmtpClientStub.send_hash[:content])
|
974
|
+
assert_emails 0, ActionMailer::Base.deliveries.*.to_s
|
975
|
+
end
|
595
976
|
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
977
|
+
should "pass the content as a proper rfc 2822 message" do
|
978
|
+
set_test_const('EventMachine::Protocols::SmtpClient', SmtpClientStub)
|
979
|
+
ExceptionHandling.log_error(exception_1)
|
980
|
+
assert EventMachineStub.block
|
981
|
+
EventMachineStub.block.call
|
982
|
+
assert DNSResolvStub.callback_block
|
983
|
+
DNSResolvStub.callback_block.call ['127.0.0.1']
|
984
|
+
assert content = SmtpClientStub.send_hash[:content]
|
985
|
+
assert_match(/Content-Transfer-Encoding: 7bit/, content)
|
986
|
+
assert_match(/\r\n\.\r\n\z/, content)
|
987
|
+
end
|
601
988
|
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
989
|
+
should "log fatal on EventMachine STMP errback" do
|
990
|
+
assert_emails 0, ActionMailer::Base.deliveries.*.to_s
|
991
|
+
set_test_const('EventMachine::Protocols::SmtpClient', SmtpClientErrbackStub)
|
992
|
+
mock(ExceptionHandling.logger).fatal(/Exception 1/, anything)
|
993
|
+
mock(ExceptionHandling.logger).fatal(/Failed to email by SMTP: "credential mismatch"/)
|
994
|
+
|
995
|
+
ExceptionHandling.log_error(exception_1)
|
996
|
+
assert EventMachineStub.block
|
997
|
+
EventMachineStub.block.call
|
998
|
+
assert DNSResolvStub.callback_block
|
999
|
+
DNSResolvStub.callback_block.call(['127.0.0.1'])
|
1000
|
+
SmtpClientErrbackStub.block.call("credential mismatch")
|
1001
|
+
assert_equal_with_diff EXPECTED_SMTP_HASH, (SmtpClientErrbackStub.send_hash & EXPECTED_SMTP_HASH.keys).map_hash { |k,v| v.to_s }, SmtpClientErrbackStub.send_hash.inspect
|
1002
|
+
#assert_emails 0, ActionMailer::Base.deliveries.*.to_s
|
1003
|
+
end
|
1004
|
+
|
1005
|
+
should "log fatal on EventMachine dns resolver errback" do
|
1006
|
+
assert_emails 0, ActionMailer::Base.deliveries.*.to_s
|
1007
|
+
mock(ExceptionHandling.logger).fatal(/Exception 1/, anything)
|
1008
|
+
mock(ExceptionHandling.logger).fatal(/Failed to resolv DNS for localhost: "softlayer sucks"/)
|
1009
|
+
|
1010
|
+
ExceptionHandling.log_error(exception_1)
|
1011
|
+
assert EventMachineStub.block
|
1012
|
+
EventMachineStub.block.call
|
1013
|
+
DNSResolvStub.errback_block.call("softlayer sucks")
|
1014
|
+
end
|
608
1015
|
end
|
609
1016
|
end
|
610
1017
|
end
|
611
|
-
end
|
612
1018
|
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
1019
|
+
should "truncate email subject" do
|
1020
|
+
ActionMailer::Base.deliveries.clear
|
1021
|
+
text = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLM".split('').join("123456789")
|
1022
|
+
begin
|
1023
|
+
raise text
|
1024
|
+
rescue => ex
|
1025
|
+
ExceptionHandling.log_error( ex )
|
1026
|
+
end
|
1027
|
+
assert_emails 1, ActionMailer::Base.deliveries.*.inspect
|
1028
|
+
mail = ActionMailer::Base.deliveries.last
|
1029
|
+
subject = "#{ExceptionHandling.email_environment} exception: RuntimeError: " + text
|
1030
|
+
assert_equal subject[0,300], mail.subject
|
620
1031
|
end
|
621
|
-
assert_emails 1, ActionMailer::Base.deliveries.*.inspect
|
622
|
-
mail = ActionMailer::Base.deliveries.last
|
623
|
-
subject = "#{ExceptionHandling.email_environment} exception: RuntimeError: " + text
|
624
|
-
assert_equal subject[0,300], mail.subject
|
625
1032
|
end
|
626
|
-
end
|
627
1033
|
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
1034
|
+
context "Exception mapping" do
|
1035
|
+
setup do
|
1036
|
+
@data = {
|
1037
|
+
:environment=>{
|
1038
|
+
'HTTP_HOST' => "localhost",
|
1039
|
+
'HTTP_REFERER' => "http://localhost/action/controller/instance",
|
1040
|
+
},
|
1041
|
+
:session=>{
|
1042
|
+
:data=>{
|
1043
|
+
:affiliate_id=> defined?(Affiliate) ? Affiliate.first.id : '1',
|
1044
|
+
:edit_mode=> true,
|
1045
|
+
:advertiser_id=> defined?(Advertiser) ? Advertiser.first.id : '1',
|
1046
|
+
:username_id=> defined?(Username) ? Username.first.id : '1',
|
1047
|
+
:user_id=> defined?(User) ? User.first.id : '1',
|
1048
|
+
:flash=>{},
|
1049
|
+
:impersonated_organization_pk=> 'Advertiser_1'
|
1050
|
+
}
|
1051
|
+
},
|
1052
|
+
:request=>{},
|
1053
|
+
:backtrace=>["[GEM_ROOT]/gems/actionpack-2.1.0/lib/action_controller/filters.rb:580:in `call_filters'", "[GEM_ROOT]/gems/actionpack-2.1.0/lib/action_controller/filters.rb:601:in `run_before_filters'"],
|
1054
|
+
:api_key=>"none",
|
1055
|
+
:error_class=>"StandardError",
|
1056
|
+
:error=>'Some error message'
|
1057
|
+
}
|
1058
|
+
end
|
653
1059
|
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
|
658
|
-
|
1060
|
+
should "clean backtraces" do
|
1061
|
+
begin
|
1062
|
+
raise "test exception"
|
1063
|
+
rescue => ex
|
1064
|
+
backtrace = ex.backtrace
|
1065
|
+
end
|
1066
|
+
result = ExceptionHandling.send(:clean_backtrace, ex).to_s
|
1067
|
+
assert_not_equal result, backtrace
|
659
1068
|
end
|
660
|
-
result = ExceptionHandling.send(:clean_backtrace, ex).to_s
|
661
|
-
assert_not_equal result, backtrace
|
662
|
-
end
|
663
1069
|
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
|
685
|
-
|
686
|
-
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
697
|
-
|
1070
|
+
should "return entire backtrace if cleaned is emtpy" do
|
1071
|
+
begin
|
1072
|
+
backtrace = ["/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activerecord/lib/active_record/relation/finder_methods.rb:312:in `find_with_ids'",
|
1073
|
+
"/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activerecord/lib/active_record/relation/finder_methods.rb:107:in `find'",
|
1074
|
+
"/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activerecord/lib/active_record/querying.rb:5:in `__send__'",
|
1075
|
+
"/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activerecord/lib/active_record/querying.rb:5:in `find'",
|
1076
|
+
"/Library/Ruby/Gems/1.8/gems/shoulda-context-1.0.2/lib/shoulda/context/context.rb:398:in `call'",
|
1077
|
+
"/Library/Ruby/Gems/1.8/gems/shoulda-context-1.0.2/lib/shoulda/context/context.rb:398:in `test: Exception mapping should return entire backtrace if cleaned is emtpy. '",
|
1078
|
+
"/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/testing/setup_and_teardown.rb:72:in `__send__'",
|
1079
|
+
"/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/testing/setup_and_teardown.rb:72:in `run'",
|
1080
|
+
"/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:447:in `_run__1913317170__setup__4__callbacks'",
|
1081
|
+
"/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:405:in `send'",
|
1082
|
+
"/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:405:in `__run_callback'",
|
1083
|
+
"/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:385:in `_run_setup_callbacks'",
|
1084
|
+
"/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:81:in `send'",
|
1085
|
+
"/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:81:in `run_callbacks'",
|
1086
|
+
"/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/testing/setup_and_teardown.rb:70:in `run'",
|
1087
|
+
"/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:34:in `run'",
|
1088
|
+
"/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:33:in `each'",
|
1089
|
+
"/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:33:in `run'",
|
1090
|
+
"/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/ui/testrunnermediator.rb:46:in `old_run_suite'",
|
1091
|
+
"(eval):12:in `run_suite'",
|
1092
|
+
"/Applications/RubyMine.app/rb/testing/patch/testunit/test/unit/ui/teamcity/testrunner.rb:93:in `send'",
|
1093
|
+
"/Applications/RubyMine.app/rb/testing/patch/testunit/test/unit/ui/teamcity/testrunner.rb:93:in `start_mediator'",
|
1094
|
+
"/Applications/RubyMine.app/rb/testing/patch/testunit/test/unit/ui/teamcity/testrunner.rb:81:in `start'",
|
1095
|
+
"/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/ui/testrunnerutilities.rb:29:in `run'",
|
1096
|
+
"/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/autorunner.rb:12:in `run'",
|
1097
|
+
"/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit.rb:279",
|
1098
|
+
"-e:1"]
|
1099
|
+
|
1100
|
+
module ::Rails
|
1101
|
+
class BacktraceCleaner
|
1102
|
+
def clean(_backtrace)
|
1103
|
+
[]
|
1104
|
+
end
|
698
1105
|
end
|
699
1106
|
end
|
700
|
-
end
|
701
1107
|
|
702
|
-
|
1108
|
+
mock(Rails).backtrace_cleaner { Rails::BacktraceCleaner.new }
|
703
1109
|
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
1110
|
+
ex = Exception.new
|
1111
|
+
ex.set_backtrace(backtrace)
|
1112
|
+
result = ExceptionHandling.send(:clean_backtrace, ex)
|
1113
|
+
assert_equal backtrace, result
|
1114
|
+
ensure
|
1115
|
+
Object.send(:remove_const, :Rails)
|
1116
|
+
end
|
710
1117
|
end
|
711
1118
|
end
|
712
1119
|
|
713
|
-
|
714
|
-
|
715
|
-
|
716
|
-
|
717
|
-
|
718
|
-
end
|
719
|
-
end
|
720
|
-
|
721
|
-
context "log_perodically" do
|
722
|
-
setup do
|
723
|
-
Time.now_override = Time.now # Freeze time
|
724
|
-
ExceptionHandling.logger.clear
|
725
|
-
end
|
1120
|
+
context "log_perodically" do
|
1121
|
+
setup do
|
1122
|
+
Time.now_override = Time.now # Freeze time
|
1123
|
+
ExceptionHandling.logger.clear
|
1124
|
+
end
|
726
1125
|
|
727
|
-
|
728
|
-
|
729
|
-
|
1126
|
+
teardown do
|
1127
|
+
Time.now_override = nil
|
1128
|
+
end
|
730
1129
|
|
731
|
-
|
732
|
-
|
1130
|
+
should "take in additional key word args as logging context and pass them to the logger" do
|
1131
|
+
ExceptionHandling.log_periodically(:test_context_with_periodic, 30.minutes, "this will be written", service_name: 'exception_handling')
|
1132
|
+
assert_not_empty logged_excluding_reload_filter.last[:context]
|
1133
|
+
assert_equal logged_excluding_reload_filter.last[:context], { service_name: 'exception_handling' }
|
1134
|
+
end
|
733
1135
|
|
734
|
-
|
735
|
-
|
1136
|
+
should "log immediately when we are expected to log" do
|
1137
|
+
ExceptionHandling.log_periodically(:test_periodic_exception, 30.minutes, "this will be written")
|
1138
|
+
assert_equal 1, logged_excluding_reload_filter.size
|
736
1139
|
|
737
|
-
|
738
|
-
|
739
|
-
|
1140
|
+
Time.now_override = Time.now + 5.minutes
|
1141
|
+
ExceptionHandling.log_periodically(:test_periodic_exception, 30.minutes, "this will not be written")
|
1142
|
+
assert_equal 1, logged_excluding_reload_filter.size
|
740
1143
|
|
741
|
-
|
742
|
-
|
1144
|
+
ExceptionHandling.log_periodically(:test_another_periodic_exception, 30.minutes, "this will be written")
|
1145
|
+
assert_equal 2, logged_excluding_reload_filter.size
|
743
1146
|
|
744
|
-
|
1147
|
+
Time.now_override = Time.now + 26.minutes
|
745
1148
|
|
746
|
-
|
747
|
-
|
1149
|
+
ExceptionHandling.log_periodically(:test_periodic_exception, 30.minutes, "this will be written")
|
1150
|
+
assert_equal 3, logged_excluding_reload_filter.size
|
1151
|
+
end
|
748
1152
|
end
|
749
1153
|
end
|
750
1154
|
|
751
1155
|
private
|
752
1156
|
|
1157
|
+
def logged_excluding_reload_filter
|
1158
|
+
ExceptionHandling.logger.logged.select { |l| l[:message] !~ /Reloading filter list/ }
|
1159
|
+
end
|
1160
|
+
|
753
1161
|
def incrementing_mtime
|
754
1162
|
@mtime ||= Time.now
|
755
1163
|
@mtime += 1.day
|