exception_handling 3.0.pre.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|