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.
Files changed (32) hide show
  1. checksums.yaml +5 -5
  2. data/.ruby-version +1 -1
  3. data/Gemfile +17 -0
  4. data/Gemfile.lock +142 -102
  5. data/README.md +11 -2
  6. data/config/exception_filters.yml +2 -0
  7. data/exception_handling.gemspec +6 -10
  8. data/lib/exception_handling.rb +222 -313
  9. data/lib/exception_handling/exception_catalog.rb +8 -6
  10. data/lib/exception_handling/exception_description.rb +8 -6
  11. data/lib/exception_handling/exception_info.rb +272 -0
  12. data/lib/exception_handling/honeybadger_callbacks.rb +42 -0
  13. data/lib/exception_handling/log_stub_error.rb +18 -3
  14. data/lib/exception_handling/mailer.rb +14 -0
  15. data/lib/exception_handling/methods.rb +26 -8
  16. data/lib/exception_handling/testing.rb +2 -2
  17. data/lib/exception_handling/version.rb +3 -1
  18. data/test/helpers/controller_helpers.rb +27 -0
  19. data/test/helpers/exception_helpers.rb +11 -0
  20. data/test/test_helper.rb +42 -19
  21. data/test/unit/exception_handling/exception_catalog_test.rb +19 -0
  22. data/test/unit/exception_handling/exception_description_test.rb +12 -1
  23. data/test/unit/exception_handling/exception_info_test.rb +501 -0
  24. data/test/unit/exception_handling/honeybadger_callbacks_test.rb +85 -0
  25. data/test/unit/exception_handling/log_error_stub_test.rb +26 -3
  26. data/test/unit/exception_handling/mailer_test.rb +39 -14
  27. data/test/unit/exception_handling/methods_test.rb +40 -18
  28. data/test/unit/exception_handling_test.rb +947 -539
  29. data/views/exception_handling/mailer/escalate_custom.html.erb +17 -0
  30. data/views/exception_handling/mailer/exception_notification.html.erb +1 -1
  31. data/views/exception_handling/mailer/log_parser_exception_notification.html.erb +1 -1
  32. 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 => ex
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 => ex
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 ActionDispatch::Assertions::SelectorAssertions
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."}).deliver
24
- assert_match /Test Error./, result.body.to_s
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").deliver
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 ).deliver
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::Document.new(result.body.to_s)
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 body_html.root, "html" do
52
- assert_select "title", "Exception Escalation"
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 &lt;b&gt;Feature&lt;b&gt; Failed", result.body.to_s
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\n &lt;i&gt;More Info&lt;i&gt;/
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::Document.new(result.body.to_s)
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 body_html.root, "html" do
76
+ assert_select "html" do
70
77
  assert_select "body i", true, result.body.to_s do |is|
71
- assert_select is[0], "i", 'no error #'
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( ) do
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(ExceptionHandling.logger).fatal(/blah/)
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
- # This was the original stub:
43
- # Rails.expects(:env).times(2).returns('production')
44
- # but this stubbing approach no longer works
45
- # Rails is setting the long controller timeout on module load
46
- # in exception_handling.rb - that happens before this test ever gets run
47
- # we can set Rails.env here, which we do, because it affects part of the real-time
48
- # logic check for whether to raise (which we want). To overcome the setting
49
- # on the long controller timeout I have set the Time.now_override to 1.day.from_now
50
- # instead of 1.hour.from_now
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( ) do
53
- Time.now_override = 1.day.from_now
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 "configuration" do
51
- def append_organization_info_config(data)
52
- begin
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
- def log_error_callback_with_failure(data, ex)
67
- raise "this should be rescued"
68
- end
109
+ context "#log_error" do
110
+ setup do
111
+ ExceptionHandling.mailer_send_enabled = true
112
+ end
69
113
 
70
- setup do
71
- @fail_count = 0
72
- end
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
- should "support a custom_data_hook" do
75
- ExceptionHandling.custom_data_hook = method(:append_organization_info_config)
76
- ExceptionHandling.ensure_safe("mooo") { raise "Some BS" }
77
- assert_match(/Invoca Engineering Dept./, ActionMailer::Base.deliveries[-1].body.to_s)
78
- ExceptionHandling.custom_data_hook = nil
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
- should "support a log_error hook and pass exception data to it" do
82
- begin
83
- ExceptionHandling.post_log_error_hook = method(:log_error_callback_config)
84
- ExceptionHandling.ensure_safe("mooo") { raise "Some BS" }
85
- assert_equal 1, @fail_count
86
- ensure
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
- assert_equal "this is used by a test", @callback_data["notes"]
91
- assert_match(/this is used by a test/, ActionMailer::Base.deliveries[-1].body.to_s)
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
- should "support rescue exceptions from a log_error hook" do
95
- ExceptionHandling.post_log_error_hook = method(:log_error_callback_with_failure)
96
- assert_nothing_raised { ExceptionHandling.ensure_safe("mooo") { raise "Some BS" } }
97
- assert_equal 0, @fail_count
98
- ExceptionHandling.post_log_error_hook = nil
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
- end
102
-
103
- context "Exception Handling" do
104
- setup do
105
- ActionMailer::Base.deliveries.clear
106
- ExceptionHandling.send(:clear_exception_summary)
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 "ExceptionHandling.ensure_safe" do
110
- should "log an exception with call stack if an exception is raised." do
111
- mock(ExceptionHandling.logger).fatal(/\(blah\):\n.*exception_handling_test\.rb/)
112
- ExceptionHandling.ensure_safe { raise ArgumentError.new("blah") }
113
- end
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 "should not log an exception if an exception is not raised." do
121
- dont_allow(ExceptionHandling.logger).fatal
122
- ExceptionHandling.ensure_safe { ; }
123
- end
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
- should "return its value if used during an assignment" do
126
- dont_allow(ExceptionHandling.logger).fatal
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 "return nil if an exception is raised during an assignment" do
132
- mock(ExceptionHandling.logger).fatal(/\(blah\):\n.*exception_handling_test\.rb/)
133
- b = ExceptionHandling.ensure_safe { raise ArgumentError.new("blah") }
134
- assert_equal nil, b
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 "allow a message to be appended to the error when logged." do
138
- mock(ExceptionHandling.logger).fatal(/mooo \(blah\):\n.*exception_handling_test\.rb/)
139
- b = ExceptionHandling.ensure_safe("mooo") { raise ArgumentError.new("blah") }
140
- assert_nil b
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 "only rescue StandardError and descendents" do
144
- assert_raise(Exception) { ExceptionHandling.ensure_safe("mooo") { raise Exception } }
145
-
146
- mock(ExceptionHandling.logger).fatal(/mooo \(blah\):\n.*exception_handling_test\.rb/)
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
- b = ExceptionHandling.ensure_safe("mooo") { raise StandardError.new("blah") }
149
- assert_nil b
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 "ExceptionHandling.ensure_completely_safe" do
154
- should "log an exception if an exception is raised." do
155
- mock(ExceptionHandling.logger).fatal(/\(blah\):\n.*exception_handling_test\.rb/)
156
- ExceptionHandling.ensure_completely_safe { raise ArgumentError.new("blah") }
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
- should "should not log an exception if an exception is not raised." do
160
- mock(ExceptionHandling.logger).fatal.times(0)
161
- ExceptionHandling.ensure_completely_safe { ; }
162
- end
246
+ context "default_metric_name" do
163
247
 
164
- should "return its value if used during an assignment" do
165
- mock(ExceptionHandling.logger).fatal.times(0)
166
- b = ExceptionHandling.ensure_completely_safe { 5 }
167
- assert_equal 5, b
168
- end
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
- should "return nil if an exception is raised during an assignment" do
171
- mock(ExceptionHandling.logger).fatal(/\(blah\):\n.*exception_handling_test\.rb/) { nil }
172
- b = ExceptionHandling.ensure_completely_safe { raise ArgumentError.new("blah") }
173
- assert_equal nil, b
174
- end
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
- should "allow a message to be appended to the error when logged." do
177
- mock(ExceptionHandling.logger).fatal(/mooo \(blah\):\n.*exception_handling_test\.rb/)
178
- b = ExceptionHandling.ensure_completely_safe("mooo") { raise ArgumentError.new("blah") }
179
- assert_nil b
180
- end
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
- should "rescue any instance or child of Exception" do
183
- mock(ExceptionHandling.logger).fatal(/\(blah\):\n.*exception_handling_test\.rb/)
184
- ExceptionHandling::ensure_completely_safe { raise Exception.new("blah") }
185
- end
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
- should "not rescue the special exceptions that Ruby uses" do
188
- [SystemExit, SystemStackError, NoMemoryError, SecurityError].each do |exception|
189
- assert_raise exception do
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
- context "ExceptionHandling.ensure_escalation" do
199
- should "log the exception as usual and send the proper email" do
200
- assert_equal 0, ActionMailer::Base.deliveries.count
201
- mock(ExceptionHandling.logger).fatal(/\(blah\):\n.*exception_handling_test\.rb/)
202
- ExceptionHandling.ensure_escalation( "Favorite Feature") { raise ArgumentError.new("blah") }
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
- should "should not escalate if an exception is not raised." do
211
- assert_equal 0, ActionMailer::Base.deliveries.count
212
- dont_allow(ExceptionHandling.logger).fatal
213
- ExceptionHandling.ensure_escalation('Ignored') { ; }
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
- should "log if the escalation email can not be sent" do
218
- any_instance_of(Mail::Message) do |message|
219
- mock(message).deliver
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
- mock(ExceptionHandling.logger) do |logger|
223
- logger.fatal(/first_test_exception/)
224
- logger.fatal(/safe_email_deliver .*Delivery Error/)
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
- context "ExceptionHandling.ensure_alert" do
232
- should "log the exception as usual and fire a sensu event" do
233
- mock(ExceptionHandling::Sensu).generate_event("Favorite Feature", "test context\nblah")
234
- mock(ExceptionHandling.logger).fatal(/\(blah\):\n.*exception_handling_test\.rb/)
235
- ExceptionHandling.ensure_alert('Favorite Feature', 'test context') { raise ArgumentError.new("blah") }
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
- should "should not send sensu event if an exception is not raised." do
239
- dont_allow(ExceptionHandling.logger).fatal
240
- dont_allow(ExceptionHandling::Sensu).generate_event
241
- ExceptionHandling.ensure_alert('Ignored', 'test context') { ; }
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
- should "log if the sensu event could not be sent" do
245
- mock(ExceptionHandling::Sensu).send_event(anything) { raise "Failed to send" }
246
- mock(ExceptionHandling.logger) do |logger|
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
- context "exception timestamp" do
255
- setup do
256
- Time.now_override = Time.parse( '1986-5-21 4:17 am UTC' )
257
- end
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
- def log_error_callback(data, ex)
260
- @fail_count += 1
261
- end
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
- should "include the timestamp when the exception is logged" do
265
- mock(ExceptionHandling.logger).fatal(/\(Error:517033020\) ArgumentError mooo \(blah\):\n.*exception_handling_test\.rb/)
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
- assert_equal 517033020, ExceptionHandling.last_exception_timestamp
348
+ mock(ExceptionHandling.logger).fatal(/mooo \(blah\):\n.*exception_handling_test\.rb/, anything)
270
349
 
271
- assert_emails 1
272
- assert_match(/517033020/, ActionMailer::Base.deliveries[-1].body.to_s)
350
+ b = ExceptionHandling.ensure_safe("mooo") { raise StandardError.new("blah") }
351
+ assert_nil b
352
+ end
273
353
  end
274
- end
275
354
 
276
- should "send just one copy of exceptions that don't repeat" do
277
- ExceptionHandling.log_error(exception_1)
278
- ExceptionHandling.log_error(exception_2)
279
- assert_emails 2
280
- assert_match(/Exception 1/, ActionMailer::Base.deliveries[-2].subject)
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
- should "only send 5 of a repeated error, but call post hook for every exception" do
285
- @fail_count = 0
286
- ExceptionHandling.post_log_error_hook = method(:log_error_callback)
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
- should "only send 5 of a repeated error but don't send summary if 6th is different" do
296
- assert_emails 5 do
297
- 5.times do
298
- ExceptionHandling.log_error(exception_1)
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
- should "send the summary when the error is encountered an hour after the first occurrence" do
307
- assert_emails 5 do # 5 exceptions, 4 summarized
308
- 9.times do |t|
309
- ExceptionHandling.log_error(exception_1)
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
- Time.now_override = 2.hours.from_now
313
- assert_emails 1 do # 1 summary (4 + 1 = 5) after 2 hours
314
- ExceptionHandling.log_error(exception_1)
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
- assert_emails 0 do # still summarizing...
320
- 7.times do
321
- ExceptionHandling.log_error(exception_1)
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
- Time.now_override = 3.hours.from_now
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
- assert_emails 1 + 2 do # 1 summary and 2 new
328
- 2.times do
329
- ExceptionHandling.log_error(exception_2)
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
- should "send the summary if a summary is available, but not sent when another exception comes up" do
337
- assert_emails 5 do # 5 to start summarizing
338
- 6.times do
339
- ExceptionHandling.log_error(exception_1)
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
- assert_emails 1 + 1 do # 1 summary of previous, 1 from new exception
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
- assert_match(/\[1 SUMMARIZED\]/, ActionMailer::Base.deliveries[-2].subject)
348
- assert_match(/This exception occurred 1 times since/, ActionMailer::Base.deliveries[-2].body.to_s)
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
- assert_emails 5 do # 5 to start summarizing
351
- 10.times do
352
- ExceptionHandling.log_error(exception_1)
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
- assert_emails 0 do # still summarizing
357
- 11.times do
358
- ExceptionHandling.log_error(exception_1)
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
- context "Honeybadger integration" do
364
- context "with Honeybadger not defined" do
365
- should "not invoke send_exception_to_honeybadger when log_error is executed" do
366
- ExceptionHandling.expects(:send_exception_to_honeybadger).times(0)
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
- should "not invoke send_exception_to_honeybadger when ensure_safe is executed" do
371
- ExceptionHandling.expects(:send_exception_to_honeybadger).times(0)
372
- ExceptionHandling.ensure_safe { raise exception_1 }
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
- context "with Honeybadger defined" do
377
- setup do
378
- stub(ExceptionHandling).honeybadger? { true }
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
- should "invoke send_exception_to_honeybadger when log_error is executed" do
382
- ExceptionHandling.expects(:send_exception_to_honeybadger).times(1)
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
- should "invoke send_exception_to_honeybadger when ensure_safe is executed" do
387
- ExceptionHandling.expects(:send_exception_to_honeybadger).times(1)
388
- ExceptionHandling.ensure_safe { raise exception_1 }
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
- class EventResponse
394
- def to_s
395
- "message from to_s!"
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
- should "allow sections to have data with just a to_s method" do
400
- ExceptionHandling.log_error("This is my RingSwitch example. Log, don't email!") do |data|
401
- data.merge!(:event_response => EventResponse.new)
402
- end
403
- assert_emails 1
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
- should "rescue exceptions that happen in log_error" do
409
- stub(ExceptionHandling).make_exception { raise ArgumentError.new("Bad argument") }
410
- mock(ExceptionHandling).write_exception_to_log(satisfy { |ex| ex.to_s['Bad argument'] },
411
- satisfy { |context| context['ExceptionHandling.log_error rescued exception while logging Runtime message'] },
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
- should "rescue exceptions that happen when log_error yields" do
418
- mock(ExceptionHandling).write_exception_to_log(satisfy { |ex| ex.to_s['Bad argument'] },
419
- satisfy { |context| context['Context message'] },
420
- anything)
421
- ExceptionHandling.log_error(ArgumentError.new("Bad argument"), "Context message") { |data| raise 'Error!!!' }
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
- context "Exception Filtering" do
425
- setup do
426
- filter_list = { :exception1 => { 'error' => "my error message" },
427
- :exception2 => { 'error' => "some other message", :session => "misc data" } }
428
- stub(YAML).load_file { ActiveSupport::HashWithIndifferentAccess.new(filter_list) }
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
- # bump modified time up to get the above filter loaded
431
- stub(File).mtime { incrementing_mtime }
432
- end
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
- should "handle case where filter list is not found" do
435
- stub(YAML).load_file { raise Errno::ENOENT.new("File not found") }
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
- ActionMailer::Base.deliveries.clear
438
- ExceptionHandling.log_error( "My error message is in list" )
439
- assert_emails 1
440
- end
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
- should "log exception and suppress email when exception is on filter list" do
443
- ActionMailer::Base.deliveries.clear
444
- ExceptionHandling.log_error( "Error message is not in list" )
445
- assert_emails 1
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
- ActionMailer::Base.deliveries.clear
448
- ExceptionHandling.log_error( "My error message is in list" )
449
- assert_emails 0
450
- end
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
- should "allow filtering exception on any text in exception data" do
453
- filters = { :exception1 => { :session => "data: my extra session data" } }
454
- stub(YAML).load_file { ActiveSupport::HashWithIndifferentAccess.new(filters) }
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
- ActionMailer::Base.deliveries.clear
457
- ExceptionHandling.log_error( "No match here" ) do |data|
458
- data[:session] = {
459
- :key => "@session_id",
460
- :data => "my extra session data"
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
- ActionMailer::Base.deliveries.clear
466
- ExceptionHandling.log_error( "No match here" ) do |data|
467
- data[:session] = {
468
- :key => "@session_id",
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
- should "reload filter list on the next exception if file was modified" do
476
- ActionMailer::Base.deliveries.clear
477
- ExceptionHandling.log_error( "Error message is not in list" )
478
- assert_emails 1
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
- filter_list = { :exception1 => { 'error' => "Error message is not in list" } }
481
- stub(YAML).load_file { ActiveSupport::HashWithIndifferentAccess.new(filter_list) }
482
- stub(File).mtime { incrementing_mtime }
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
- ActionMailer::Base.deliveries.clear
485
- ExceptionHandling.log_error( "Error message is not in list" )
486
- assert_emails 0, ActionMailer::Base.deliveries.*.body.*.inspect
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
- should "not consider filter if both error message and body do not match" do
490
- # error message matches, but not full text
491
- ActionMailer::Base.deliveries.clear
492
- ExceptionHandling.log_error( "some other message" )
493
- assert_emails 1, ActionMailer::Base.deliveries.*.body.*.inspect
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
- # now both match
496
- ActionMailer::Base.deliveries.clear
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
- should "skip environment keys not on whitelist" do
504
- ActionMailer::Base.deliveries.clear
505
- ExceptionHandling.log_error( "some message" ) do |data|
506
- data[:environment] = { :SERVER_PROTOCOL => "HTTP/1.0", :RAILS_SECRETS_YML_CONTENTS => 'password: VERY_SECRET_PASSWORD' }
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
- should "omit environment defaults" do
515
- ActionMailer::Base.deliveries.clear
516
- ExceptionHandling.log_error( "some message" ) do |data|
517
- data[:environment] = {:SERVER_PORT => '80', :SERVER_PROTOCOL => "HTTP/1.0"}
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
- should "reject the filter file if any contain all empty regexes" do
526
- filter_list = { :exception1 => { 'error' => "", :session => "" },
527
- :exception2 => { 'error' => "is not in list", :session => "" } }
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
- ActionMailer::Base.deliveries.clear
532
- ExceptionHandling.log_error( "Error message is not in list" )
533
- assert_emails 1, ActionMailer::Base.deliveries.*.inspect
534
- end
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
- context "Exception Handling Mailer" do
537
- should "create email" do
538
- ExceptionHandling.log_error(exception_1) do |data|
539
- data[:request] = { :params => {:id => 10993}, :url => "www.ringrevenue.com" }
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
- assert mail = ActionMailer::Base.deliveries.last
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
- teardown do
570
- ExceptionHandling.eventmachine_safe = false
571
- ExceptionHandling.eventmachine_synchrony = false
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
- should "schedule EventMachine STMP when EventMachine defined" do
575
- set_test_const('EventMachine::Protocols::SmtpClient', SmtpClientStub)
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
- ExceptionHandling.log_error(exception_1)
578
- assert EventMachineStub.block
579
- EventMachineStub.block.call
580
- 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
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
- should "pass the content as a proper rfc 2822 message" do
587
- set_test_const('EventMachine::Protocols::SmtpClient', SmtpClientStub)
588
- ExceptionHandling.log_error(exception_1)
589
- assert EventMachineStub.block
590
- EventMachineStub.block.call
591
- assert content = SmtpClientStub.send_hash[:content]
592
- assert_match(/Content-Transfer-Encoding: 7bit/, content)
593
- assert_match(/\r\n\.\r\n\z/, content)
594
- end
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
- should "log fatal on EventMachine STMP errback" do
597
- assert_emails 0, ActionMailer::Base.deliveries.*.to_s
598
- set_test_const('EventMachine::Protocols::SmtpClient', SmtpClientErrbackStub)
599
- mock(ExceptionHandling.logger).fatal(/Exception 1/)
600
- mock(ExceptionHandling.logger).fatal(/Failed to email by SMTP: "credential mismatch"/)
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
- ExceptionHandling.log_error(exception_1)
603
- assert EventMachineStub.block
604
- EventMachineStub.block.call
605
- SmtpClientErrbackStub.block.call("credential mismatch")
606
- 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
607
- #assert_emails 0, ActionMailer::Base.deliveries.*.to_s
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
- should "truncate email subject" do
614
- ActionMailer::Base.deliveries.clear
615
- text = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLM".split('').join("123456789")
616
- begin
617
- raise text
618
- rescue => ex
619
- ExceptionHandling.log_error( ex )
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
- context "Exception mapping" do
629
- setup do
630
- @data = {
631
- :environment=>{
632
- 'HTTP_HOST' => "localhost",
633
- 'HTTP_REFERER' => "http://localhost/action/controller/instance",
634
- },
635
- :session=>{
636
- :data=>{
637
- :affiliate_id=> defined?(Affiliate) ? Affiliate.first.id : '1',
638
- :edit_mode=> true,
639
- :advertiser_id=> defined?(Advertiser) ? Advertiser.first.id : '1',
640
- :username_id=> defined?(Username) ? Username.first.id : '1',
641
- :user_id=> defined?(User) ? User.first.id : '1',
642
- :flash=>{},
643
- :impersonated_organization_pk=> 'Advertiser_1'
644
- }
645
- },
646
- :request=>{},
647
- :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'"],
648
- :api_key=>"none",
649
- :error_class=>"StandardError",
650
- :error=>'Some error message'
651
- }
652
- end
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
- should "clean backtraces" do
655
- begin
656
- raise "test exception"
657
- rescue => ex
658
- backtrace = ex.backtrace
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
- should "return entire backtrace if cleaned is emtpy" do
665
- begin
666
- backtrace = ["/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activerecord/lib/active_record/relation/finder_methods.rb:312:in `find_with_ids'",
667
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activerecord/lib/active_record/relation/finder_methods.rb:107:in `find'",
668
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activerecord/lib/active_record/querying.rb:5:in `__send__'",
669
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activerecord/lib/active_record/querying.rb:5:in `find'",
670
- "/Library/Ruby/Gems/1.8/gems/shoulda-context-1.0.2/lib/shoulda/context/context.rb:398:in `call'",
671
- "/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. '",
672
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/testing/setup_and_teardown.rb:72:in `__send__'",
673
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/testing/setup_and_teardown.rb:72:in `run'",
674
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:447:in `_run__1913317170__setup__4__callbacks'",
675
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:405:in `send'",
676
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:405:in `__run_callback'",
677
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:385:in `_run_setup_callbacks'",
678
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:81:in `send'",
679
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/callbacks.rb:81:in `run_callbacks'",
680
- "/Users/peter/ringrevenue/web/vendor/rails-3.2.12/activesupport/lib/active_support/testing/setup_and_teardown.rb:70:in `run'",
681
- "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:34:in `run'",
682
- "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:33:in `each'",
683
- "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/testsuite.rb:33:in `run'",
684
- "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/ui/testrunnermediator.rb:46:in `old_run_suite'",
685
- "(eval):12:in `run_suite'",
686
- "/Applications/RubyMine.app/rb/testing/patch/testunit/test/unit/ui/teamcity/testrunner.rb:93:in `send'",
687
- "/Applications/RubyMine.app/rb/testing/patch/testunit/test/unit/ui/teamcity/testrunner.rb:93:in `start_mediator'",
688
- "/Applications/RubyMine.app/rb/testing/patch/testunit/test/unit/ui/teamcity/testrunner.rb:81:in `start'",
689
- "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/ui/testrunnerutilities.rb:29:in `run'",
690
- "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit/autorunner.rb:12:in `run'",
691
- "/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/1.8/test/unit.rb:279",
692
- "-e:1"]
693
-
694
- module ::Rails
695
- class BacktraceCleaner
696
- def clean(_backtrace)
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
- mock(Rails).backtrace_cleaner { Rails::BacktraceCleaner.new }
1108
+ mock(Rails).backtrace_cleaner { Rails::BacktraceCleaner.new }
703
1109
 
704
- ex = Exception.new
705
- ex.set_backtrace(backtrace)
706
- result = ExceptionHandling.send(:clean_backtrace, ex)
707
- assert_equal backtrace, result
708
- ensure
709
- Object.send(:remove_const, :Rails)
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
- should "clean params" do
714
- p = {'password' => 'apple', 'username' => 'sam' }
715
- ExceptionHandling.send( :clean_params, p )
716
- assert_equal "[FILTERED]", p['password']
717
- assert_equal 'sam', p['username']
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
- teardown do
728
- Time.now_override = nil
729
- end
1126
+ teardown do
1127
+ Time.now_override = nil
1128
+ end
730
1129
 
731
- should "log immediately when we are expected to log" do
732
- logger_stub = ExceptionHandling.logger
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
- ExceptionHandling.log_periodically(:test_periodic_exception, 30.minutes, "this will be written")
735
- assert_equal 1, logger_stub.logged.size
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
- Time.now_override = Time.now + 5.minutes
738
- ExceptionHandling.log_periodically(:test_periodic_exception, 30.minutes, "this will not be written")
739
- assert_equal 1, logger_stub.logged.size
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
- ExceptionHandling.log_periodically(:test_another_periodic_exception, 30.minutes, "this will be written")
742
- assert_equal 2, logger_stub.logged.size
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
- Time.now_override = Time.now + 26.minutes
1147
+ Time.now_override = Time.now + 26.minutes
745
1148
 
746
- ExceptionHandling.log_periodically(:test_periodic_exception, 30.minutes, "this will be written")
747
- assert_equal 3, logger_stub.logged.size
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