exception_handling 3.0.pre.1 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.github/CODEOWNERS +1 -0
- data/.github/workflows/pipeline.yml +36 -0
- data/.gitignore +3 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -1
- data/.tool-versions +1 -0
- data/Appraisals +13 -0
- data/CHANGELOG.md +150 -0
- data/Gemfile +10 -16
- data/Gemfile.lock +65 -128
- data/README.md +51 -19
- data/Rakefile +8 -11
- data/exception_handling.gemspec +11 -13
- data/gemfiles/rails_5.gemfile +16 -0
- data/gemfiles/rails_6.gemfile +16 -0
- data/gemfiles/rails_7.gemfile +16 -0
- data/lib/exception_handling/escalate_callback.rb +19 -0
- data/lib/exception_handling/exception_info.rb +15 -11
- data/lib/exception_handling/log_stub_error.rb +2 -1
- data/lib/exception_handling/logging_methods.rb +21 -0
- data/lib/exception_handling/testing.rb +9 -12
- data/lib/exception_handling/version.rb +1 -1
- data/lib/exception_handling.rb +83 -173
- data/{test → spec}/helpers/exception_helpers.rb +2 -2
- data/spec/rake_test_warning_false.rb +20 -0
- data/{test/test_helper.rb → spec/spec_helper.rb} +63 -66
- data/spec/unit/exception_handling/escalate_callback_spec.rb +81 -0
- data/spec/unit/exception_handling/exception_catalog_spec.rb +85 -0
- data/spec/unit/exception_handling/exception_description_spec.rb +82 -0
- data/{test/unit/exception_handling/exception_info_test.rb → spec/unit/exception_handling/exception_info_spec.rb} +170 -114
- data/{test/unit/exception_handling/log_error_stub_test.rb → spec/unit/exception_handling/log_error_stub_spec.rb} +38 -22
- data/spec/unit/exception_handling/logging_methods_spec.rb +38 -0
- data/spec/unit/exception_handling_spec.rb +1063 -0
- metadata +62 -91
- data/lib/exception_handling/honeybadger_callbacks.rb +0 -59
- data/lib/exception_handling/mailer.rb +0 -70
- data/lib/exception_handling/methods.rb +0 -101
- data/lib/exception_handling/sensu.rb +0 -28
- data/semaphore_ci/setup.sh +0 -3
- data/test/unit/exception_handling/exception_catalog_test.rb +0 -85
- data/test/unit/exception_handling/exception_description_test.rb +0 -82
- data/test/unit/exception_handling/honeybadger_callbacks_test.rb +0 -122
- data/test/unit/exception_handling/mailer_test.rb +0 -98
- data/test/unit/exception_handling/methods_test.rb +0 -84
- data/test/unit/exception_handling/sensu_test.rb +0 -52
- data/test/unit/exception_handling_test.rb +0 -1109
- data/views/exception_handling/mailer/escalate_custom.html.erb +0 -17
- data/views/exception_handling/mailer/escalation_notification.html.erb +0 -17
- data/views/exception_handling/mailer/log_parser_exception_notification.html.erb +0 -82
- /data/{test → spec}/helpers/controller_helpers.rb +0 -0
@@ -1,122 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require File.expand_path('../../test_helper', __dir__)
|
4
|
-
|
5
|
-
module ExceptionHandling
|
6
|
-
class HoneybadgerCallbacksTest < ActiveSupport::TestCase
|
7
|
-
|
8
|
-
class TestPoroWithAttribute
|
9
|
-
attr_reader :test_attribute
|
10
|
-
|
11
|
-
def initialize
|
12
|
-
@test_attribute = 'test'
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
class TestPoroWithFilteredAttribute
|
17
|
-
attr_reader :password
|
18
|
-
|
19
|
-
def initialize
|
20
|
-
@password = 'secret'
|
21
|
-
end
|
22
|
-
end
|
23
|
-
|
24
|
-
class TestPoroWithFilteredAttributeAndId < TestPoroWithFilteredAttribute
|
25
|
-
attr_reader :id
|
26
|
-
|
27
|
-
def initialize
|
28
|
-
super
|
29
|
-
@id = 1
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
class TestPoroWithFilteredAttributePkAndId < TestPoroWithFilteredAttributeAndId
|
34
|
-
def to_pk
|
35
|
-
'TestPoroWithFilteredAttributePkAndId_1'
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
class TestRaiseOnInspect < TestPoroWithAttribute
|
40
|
-
def inspect
|
41
|
-
raise "some error"
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
class TestRaiseOnInspectWithId < TestRaiseOnInspect
|
46
|
-
def id
|
47
|
-
123
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
class TestRaiseOnInspectWithToPk < TestRaiseOnInspect
|
52
|
-
def to_pk
|
53
|
-
"SomeRecord-123"
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
context "register_callbacks" do
|
58
|
-
should "store the callbacks in the honeybadger object" do
|
59
|
-
HoneybadgerCallbacks.register_callbacks
|
60
|
-
result = Honeybadger.config.local_variable_filter.call(:variable_name, 'test', [])
|
61
|
-
assert_equal('test', result)
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
context "local_variable_filter" do
|
66
|
-
should "not inspect String, Hash, Array, Set, Numeric, TrueClass, FalseClass, NilClass" do
|
67
|
-
[
|
68
|
-
['test', String],
|
69
|
-
[{ a: 1 }, Hash],
|
70
|
-
[[1, 2], Array],
|
71
|
-
[Set.new([1, 2]), Set],
|
72
|
-
[4.5, Numeric],
|
73
|
-
[true, TrueClass],
|
74
|
-
[false, FalseClass],
|
75
|
-
[nil, NilClass]
|
76
|
-
].each do |object, expected_class|
|
77
|
-
result = HoneybadgerCallbacks.send(:local_variable_filter, :variable_name, object, [])
|
78
|
-
assert result.is_a?(expected_class), "Expected #{expected_class.name} but got #{result.class.name}"
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
should "inspect other classes" do
|
83
|
-
result = HoneybadgerCallbacks.send(:local_variable_filter, :variable_name, TestPoroWithAttribute.new, ['password'])
|
84
|
-
assert_match(/#<ExceptionHandling::HoneybadgerCallbacksTest::TestPoroWithAttribute:.* @test_attribute="test">/, result)
|
85
|
-
end
|
86
|
-
|
87
|
-
context "when inspect raises exceptions" do
|
88
|
-
should "handle exceptions for objects" do
|
89
|
-
result = HoneybadgerCallbacks.send(:local_variable_filter, :variable_name, TestRaiseOnInspect.new, ['password'])
|
90
|
-
assert_equal "#<ExceptionHandling::HoneybadgerCallbacksTest::TestRaiseOnInspect [error 'RuntimeError: some error' while calling #inspect]>", result
|
91
|
-
end
|
92
|
-
|
93
|
-
should "handle exceptions for objects responding to id" do
|
94
|
-
result = HoneybadgerCallbacks.send(:local_variable_filter, :variable_name, TestRaiseOnInspectWithId.new, ['password'])
|
95
|
-
assert_equal "#<ExceptionHandling::HoneybadgerCallbacksTest::TestRaiseOnInspectWithId @id=123 [error 'RuntimeError: some error' while calling #inspect]>", result
|
96
|
-
end
|
97
|
-
|
98
|
-
should "handle exceptions for objects responding to to_pik" do
|
99
|
-
result = HoneybadgerCallbacks.send(:local_variable_filter, :variable_name, TestRaiseOnInspectWithToPk.new, ['password'])
|
100
|
-
assert_equal "#<ExceptionHandling::HoneybadgerCallbacksTest::TestRaiseOnInspectWithToPk @pk=SomeRecord-123 [error 'RuntimeError: some error' while calling #inspect]>", result
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
context "not inspect objects that contain filter keys" do
|
105
|
-
should "use to_pk if available, even if id is available" do
|
106
|
-
result = HoneybadgerCallbacks.send(:local_variable_filter, :variable_name, TestPoroWithFilteredAttributePkAndId.new, ['password'])
|
107
|
-
assert_match(/#<ExceptionHandling::HoneybadgerCallbacksTest::TestPoroWithFilteredAttributePkAndId @pk=TestPoroWithFilteredAttributePkAndId_1, \[FILTERED\]>/, result)
|
108
|
-
end
|
109
|
-
|
110
|
-
should "use id if to_pk is not available" do
|
111
|
-
result = HoneybadgerCallbacks.send(:local_variable_filter, :variable_name, TestPoroWithFilteredAttributeAndId.new, ['password'])
|
112
|
-
assert_match(/#<ExceptionHandling::HoneybadgerCallbacksTest::TestPoroWithFilteredAttributeAndId @id=1, \[FILTERED\]>/, result)
|
113
|
-
end
|
114
|
-
|
115
|
-
should "print the object name if no id or to_pk" do
|
116
|
-
result = HoneybadgerCallbacks.send(:local_variable_filter, :variable_name, TestPoroWithFilteredAttribute.new, ['password'])
|
117
|
-
assert_match(/#<ExceptionHandling::HoneybadgerCallbacksTest::TestPoroWithFilteredAttribute \[FILTERED\]>/, result)
|
118
|
-
end
|
119
|
-
end
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
@@ -1,98 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require File.expand_path('../../test_helper', __dir__)
|
4
|
-
|
5
|
-
module ExceptionHandling
|
6
|
-
class MailerTest < ActionMailer::TestCase
|
7
|
-
|
8
|
-
include ::Rails::Dom::Testing::Assertions::SelectorAssertions
|
9
|
-
tests ExceptionHandling::Mailer
|
10
|
-
|
11
|
-
def dont_stub_log_error
|
12
|
-
true
|
13
|
-
end
|
14
|
-
|
15
|
-
context "ExceptionHandling::Mailer" do
|
16
|
-
setup do
|
17
|
-
ExceptionHandling.email_environment = 'Test'
|
18
|
-
ExceptionHandling.sender_address = %("Test Exception Mailer" <null_exception@invoca.com>)
|
19
|
-
ExceptionHandling.exception_recipients = ['test_exception@invoca.com']
|
20
|
-
ExceptionHandling.escalation_recipients = ['test_escalation@invoca.com']
|
21
|
-
end
|
22
|
-
|
23
|
-
context "log_parser_exception_notification" do
|
24
|
-
should "send with string" do
|
25
|
-
result = ExceptionHandling::Mailer.log_parser_exception_notification("This is my fake error", "My Fake Subj").deliver_now
|
26
|
-
assert_equal "Test exception: My Fake Subj: This is my fake error", result.subject
|
27
|
-
assert_match(/This is my fake error/, result.body.to_s)
|
28
|
-
assert_emails 1
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
context "escalation_notification" do
|
33
|
-
setup do
|
34
|
-
def document_root_element
|
35
|
-
@body_html.root
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
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_now
|
44
|
-
|
45
|
-
assert_emails 1
|
46
|
-
result = ActionMailer::Base.deliveries.last
|
47
|
-
@body_html = Nokogiri::HTML(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 "title", "Exception Escalation"
|
52
|
-
assert_select "html" do
|
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/
|
57
|
-
assert_select "body", /\n <i>More Info<i>/
|
58
|
-
assert_select "body", /test-fe3/
|
59
|
-
# assert_select "body", /#{Web::Application::GIT_REVISION}/
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
should "use defaults for missing fields" do
|
64
|
-
result = ExceptionHandling::Mailer.escalation_notification("Your Favorite Feature Failed", error_string: "It failed because of an error\n More Info")
|
65
|
-
@body_html = Nokogiri::HTML(result.body.to_s)
|
66
|
-
|
67
|
-
assert_equal_with_diff ['test_escalation@invoca.com'], result.to
|
68
|
-
assert_equal ["null_escalation@invoca.com"], result.from
|
69
|
-
assert_equal 'Test Escalation: Your Favorite Feature Failed', result.subject
|
70
|
-
assert_select "html" do
|
71
|
-
assert_select "body i", true, result.body.to_s do |is|
|
72
|
-
assert_select is, "i", 'no error #'
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
context "ExceptionHandling.escalate_to_production_support" do
|
78
|
-
setup do
|
79
|
-
Time.now_override = Time.parse('1986-5-21 4:17 am UTC')
|
80
|
-
end
|
81
|
-
|
82
|
-
should "notify production support" do
|
83
|
-
subject = "Runtime Error found!"
|
84
|
-
exception = RuntimeError.new("Test")
|
85
|
-
recipients = ["prodsupport@example.com"]
|
86
|
-
|
87
|
-
ExceptionHandling.production_support_recipients = recipients
|
88
|
-
ExceptionHandling.last_exception_timestamp = Time.now.to_i
|
89
|
-
|
90
|
-
mock(ExceptionHandling).escalate(subject, exception, Time.now.to_i, recipients)
|
91
|
-
ExceptionHandling.escalate_to_production_support(exception, subject)
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
end
|
98
|
-
end
|
@@ -1,84 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require File.expand_path('../../test_helper', __dir__)
|
4
|
-
|
5
|
-
require "exception_handling/testing"
|
6
|
-
|
7
|
-
module ExceptionHandling
|
8
|
-
class MethodsTest < ActiveSupport::TestCase
|
9
|
-
include ExceptionHelpers
|
10
|
-
|
11
|
-
def dont_stub_log_error
|
12
|
-
true
|
13
|
-
end
|
14
|
-
|
15
|
-
context "ExceptionHandling.Methods" do
|
16
|
-
setup do
|
17
|
-
@controller = Testing::ControllerStub.new
|
18
|
-
ExceptionHandling.stub_handler = nil
|
19
|
-
end
|
20
|
-
|
21
|
-
should "set the around filter" do
|
22
|
-
assert_equal :set_current_controller, Testing::ControllerStub.around_filter_method
|
23
|
-
assert_nil ExceptionHandling.current_controller
|
24
|
-
@controller.simulate_around_filter do
|
25
|
-
assert_equal @controller, ExceptionHandling.current_controller
|
26
|
-
end
|
27
|
-
assert_nil ExceptionHandling.current_controller
|
28
|
-
end
|
29
|
-
|
30
|
-
should "use the current_controller when available" do
|
31
|
-
capture_notifications
|
32
|
-
|
33
|
-
mock(ExceptionHandling.logger).fatal(/blah/, anything)
|
34
|
-
@controller.simulate_around_filter do
|
35
|
-
ExceptionHandling.log_error(ArgumentError.new("blah"))
|
36
|
-
assert_equal 1, sent_notifications.size, sent_notifications.inspect
|
37
|
-
assert_match(@controller.request.request_uri, sent_notifications.last.enhanced_data['request'].to_s)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
should "report long running controller action" do
|
42
|
-
assert_equal 2, @controller.send(:long_controller_action_timeout)
|
43
|
-
mock(ExceptionHandling).log_error(/Long controller action detected in #{@controller.class.name.split("::").last}::test_action/, anything, anything)
|
44
|
-
@controller.simulate_around_filter do
|
45
|
-
sleep(3)
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
should "not report long running controller actions if it is less than the timeout" do
|
50
|
-
assert_equal 2, @controller.send(:long_controller_action_timeout)
|
51
|
-
stub(ExceptionHandling).log_error { flunk "Should not timeout" }
|
52
|
-
@controller.simulate_around_filter do
|
53
|
-
sleep(1)
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
should "default long running controller action(300/30 for test/prod)" do
|
58
|
-
class DummyController
|
59
|
-
include ExceptionHandling::Methods
|
60
|
-
end
|
61
|
-
|
62
|
-
controller = DummyController.new
|
63
|
-
|
64
|
-
Rails.env = 'production'
|
65
|
-
assert_equal 30, controller.send(:long_controller_action_timeout)
|
66
|
-
|
67
|
-
Rails.env = 'test'
|
68
|
-
assert_equal 300, controller.send(:long_controller_action_timeout)
|
69
|
-
end
|
70
|
-
|
71
|
-
context "#log_warning" do
|
72
|
-
should "be available to the controller" do
|
73
|
-
assert_equal true, @controller.methods.include?(:log_warning)
|
74
|
-
end
|
75
|
-
|
76
|
-
should "call ExceptionHandling#log_warning" do
|
77
|
-
mock(ExceptionHandling).log_warning("Hi mom")
|
78
|
-
@controller.send(:log_warning, "Hi mom")
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
end
|
84
|
-
end
|
@@ -1,52 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require File.expand_path('../../test_helper', __dir__)
|
4
|
-
|
5
|
-
module ExceptionHandling
|
6
|
-
class SensuTest < ActiveSupport::TestCase
|
7
|
-
context "#generate_event" do
|
8
|
-
should "create an event" do
|
9
|
-
mock(ExceptionHandling::Sensu).send_event(name: "world_is_ending", output: "stick head between knees and kiss ass goodbye", status: 1)
|
10
|
-
|
11
|
-
ExceptionHandling::Sensu.generate_event("world_is_ending", "stick head between knees and kiss ass goodbye")
|
12
|
-
end
|
13
|
-
|
14
|
-
should "add the sensu prefix" do
|
15
|
-
ExceptionHandling.sensu_prefix = "cnn_"
|
16
|
-
|
17
|
-
mock(ExceptionHandling::Sensu).send_event(name: "cnn_world_is_ending", output: "stick head between knees and kiss ass goodbye", status: 1)
|
18
|
-
|
19
|
-
ExceptionHandling::Sensu.generate_event("world_is_ending", "stick head between knees and kiss ass goodbye")
|
20
|
-
end
|
21
|
-
|
22
|
-
should "allow the level to be set to critical" do
|
23
|
-
mock(ExceptionHandling::Sensu).send_event(name: "world_is_ending", output: "stick head between knees and kiss ass goodbye", status: 2)
|
24
|
-
|
25
|
-
ExceptionHandling::Sensu.generate_event("world_is_ending", "stick head between knees and kiss ass goodbye", :critical)
|
26
|
-
end
|
27
|
-
|
28
|
-
should "error if an invalid level is supplied" do
|
29
|
-
dont_allow(ExceptionHandling::Sensu).send_event
|
30
|
-
|
31
|
-
assert_raise(RuntimeError, "Invalid alert level") do
|
32
|
-
ExceptionHandling::Sensu.generate_event("world_is_ending", "stick head between knees and kiss ass goodbye", :hair_on_fire)
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
context "#send_event" do
|
38
|
-
setup do
|
39
|
-
@event = { name: "world_is_ending", output: "stick head between knees and kiss ass goodbye", status: 1 }
|
40
|
-
@socket = SocketStub.new
|
41
|
-
end
|
42
|
-
|
43
|
-
should "send event json to sensu client" do
|
44
|
-
mock.any_instance_of(Addrinfo).connect.with_any_args { @socket }
|
45
|
-
|
46
|
-
ExceptionHandling::Sensu.send_event(@event)
|
47
|
-
|
48
|
-
assert_equal @event.to_json, @socket.sent.first
|
49
|
-
end
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end
|