exception_notification 4.6.0 → 5.0.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 (60) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.rdoc +30 -0
  3. data/CONTRIBUTING.md +23 -51
  4. data/README.md +65 -31
  5. data/Rakefile +14 -7
  6. data/exception_notification.gemspec +36 -32
  7. data/lib/exception_notification/rack.rb +4 -4
  8. data/lib/exception_notification/rails.rb +2 -2
  9. data/lib/exception_notification/rake.rb +3 -7
  10. data/lib/exception_notification/resque.rb +2 -2
  11. data/lib/exception_notification/sidekiq.rb +8 -23
  12. data/lib/exception_notification/version.rb +1 -1
  13. data/lib/exception_notification.rb +3 -3
  14. data/lib/exception_notifier/datadog_notifier.rb +26 -26
  15. data/lib/exception_notifier/email_notifier.rb +34 -30
  16. data/lib/exception_notifier/google_chat_notifier.rb +9 -9
  17. data/lib/exception_notifier/hipchat_notifier.rb +12 -12
  18. data/lib/exception_notifier/irc_notifier.rb +6 -6
  19. data/lib/exception_notifier/mattermost_notifier.rb +13 -13
  20. data/lib/exception_notifier/modules/error_grouping.rb +5 -5
  21. data/lib/exception_notifier/modules/formatter.rb +12 -12
  22. data/lib/exception_notifier/notifier.rb +3 -3
  23. data/lib/exception_notifier/slack_notifier.rb +16 -16
  24. data/lib/exception_notifier/sns_notifier.rb +9 -9
  25. data/lib/exception_notifier/teams_notifier.rb +61 -57
  26. data/lib/exception_notifier/webhook_notifier.rb +3 -3
  27. data/lib/exception_notifier.rb +27 -26
  28. data/lib/generators/exception_notification/install_generator.rb +7 -7
  29. data/lib/generators/exception_notification/templates/exception_notification.rb.erb +26 -27
  30. metadata +41 -110
  31. data/Appraisals +0 -9
  32. data/Gemfile +0 -5
  33. data/Gemfile.lock +0 -352
  34. data/gemfiles/rails5_2.gemfile +0 -7
  35. data/gemfiles/rails6_0.gemfile +0 -7
  36. data/gemfiles/rails6_1.gemfile +0 -7
  37. data/gemfiles/rails7_0.gemfile +0 -7
  38. data/test/exception_notification/rack_test.rb +0 -106
  39. data/test/exception_notification/rake_test.rb +0 -38
  40. data/test/exception_notification/resque_test.rb +0 -54
  41. data/test/exception_notifier/datadog_notifier_test.rb +0 -153
  42. data/test/exception_notifier/email_notifier_test.rb +0 -351
  43. data/test/exception_notifier/google_chat_notifier_test.rb +0 -185
  44. data/test/exception_notifier/hipchat_notifier_test.rb +0 -218
  45. data/test/exception_notifier/irc_notifier_test.rb +0 -139
  46. data/test/exception_notifier/mattermost_notifier_test.rb +0 -251
  47. data/test/exception_notifier/modules/error_grouping_test.rb +0 -167
  48. data/test/exception_notifier/modules/formatter_test.rb +0 -152
  49. data/test/exception_notifier/sidekiq_test.rb +0 -34
  50. data/test/exception_notifier/slack_notifier_test.rb +0 -229
  51. data/test/exception_notifier/sns_notifier_test.rb +0 -178
  52. data/test/exception_notifier/teams_notifier_test.rb +0 -92
  53. data/test/exception_notifier/webhook_notifier_test.rb +0 -98
  54. data/test/exception_notifier_test.rb +0 -288
  55. data/test/support/exception_notifier_helper.rb +0 -14
  56. data/test/support/views/exception_notifier/_new_bkg_section.html.erb +0 -1
  57. data/test/support/views/exception_notifier/_new_bkg_section.text.erb +0 -1
  58. data/test/support/views/exception_notifier/_new_section.html.erb +0 -1
  59. data/test/support/views/exception_notifier/_new_section.text.erb +0 -1
  60. data/test/test_helper.rb +0 -19
@@ -1,251 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'test_helper'
4
- require 'httparty'
5
- require 'timecop'
6
- require 'json'
7
-
8
- class MattermostNotifierTest < ActiveSupport::TestCase
9
- URL = 'http://localhost:8000'
10
-
11
- def setup
12
- Timecop.freeze('2018-12-09 12:07:16 UTC')
13
- end
14
-
15
- def teardown
16
- Timecop.return
17
- end
18
-
19
- test 'should send notification if properly configured' do
20
- opts = {
21
- body: default_body.to_json,
22
- headers: default_headers
23
- }
24
-
25
- HTTParty.expects(:post).with(URL, opts)
26
- notifier.call ArgumentError.new('foo')
27
- end
28
-
29
- test 'should send notification with create issue link if specified' do
30
- body = default_body.merge(
31
- text: [
32
- '@channel',
33
- error_occurred_in,
34
- 'An *ArgumentError* occurred.',
35
- '*foo*',
36
- github_link
37
- ].join("\n")
38
- )
39
-
40
- opts = {
41
- body: body.to_json,
42
- headers: default_headers
43
- }
44
-
45
- HTTParty.expects(:post).with(URL, opts)
46
- notifier.call ArgumentError.new('foo'), git_url: 'github.com/aschen'
47
- end
48
-
49
- test 'should add username and icon_url params to the notification if specified' do
50
- body = default_body.merge(
51
- username: 'Test Bot',
52
- icon_url: 'http://site.com/icon.png'
53
- )
54
-
55
- opts = {
56
- body: body.to_json,
57
- headers: default_headers
58
- }
59
-
60
- HTTParty.expects(:post).with(URL, opts)
61
- notifier.call(
62
- ArgumentError.new('foo'),
63
- username: 'Test Bot',
64
- avatar: 'http://site.com/icon.png'
65
- )
66
- end
67
-
68
- test 'should add other HTTParty options to params' do
69
- opts = {
70
- basic_auth: {
71
- username: 'clara',
72
- password: 'password'
73
- },
74
- body: default_body.to_json,
75
- headers: default_headers
76
- }
77
-
78
- HTTParty.expects(:post).with(URL, opts)
79
- notifier.call(
80
- ArgumentError.new('foo'),
81
- basic_auth: {
82
- username: 'clara',
83
- password: 'password'
84
- }
85
- )
86
- end
87
-
88
- test "should use 'An' for exceptions count if :accumulated_errors_count option is nil" do
89
- opts = {
90
- body: default_body.to_json,
91
- headers: default_headers
92
- }
93
-
94
- HTTParty.expects(:post).with(URL, opts)
95
- notifier.call(ArgumentError.new('foo'))
96
- end
97
-
98
- test 'shoud use direct errors count if :accumulated_errors_count option is 5' do
99
- body = default_body.merge(
100
- text: [
101
- '@channel',
102
- error_occurred_in,
103
- '5 *ArgumentError* occurred.',
104
- '*foo*'
105
- ].join("\n")
106
- )
107
-
108
- opts = {
109
- body: body.to_json,
110
- headers: default_headers
111
- }
112
-
113
- HTTParty.expects(:post).with(URL, opts)
114
- notifier.call(ArgumentError.new('foo'), accumulated_errors_count: 5)
115
- end
116
-
117
- test 'should include backtrace and request info' do
118
- body = default_body.merge(text: [
119
- '@channel',
120
- error_occurred_in,
121
- 'An *ArgumentError* occurred.',
122
- '*foo*',
123
- request_info,
124
- backtrace_info
125
- ].join("\n"))
126
-
127
- opts = {
128
- body: body.to_json,
129
- headers: default_headers
130
- }
131
-
132
- HTTParty.expects(:post).with(URL, opts)
133
-
134
- exception = ArgumentError.new('foo')
135
- exception.set_backtrace([
136
- "app/controllers/my_controller.rb:53:in `my_controller_params'",
137
- "app/controllers/my_controller.rb:34:in `update'"
138
- ])
139
-
140
- notifier.call(exception, env: test_env)
141
- end
142
-
143
- test 'should include exception_data_info' do
144
- body = default_body.merge(
145
- text: [
146
- '@channel',
147
- error_occurred_in,
148
- 'An *ArgumentError* occurred.',
149
- '*foo*',
150
- request_info,
151
- exception_data_info
152
- ].join("\n")
153
- )
154
-
155
- opts = {
156
- body: body.to_json,
157
- headers: default_headers
158
- }
159
-
160
- env = test_env.merge(
161
- 'exception_notifier.exception_data' => { foo: 'bar', john: 'doe' }
162
- )
163
-
164
- HTTParty.expects(:post).with(URL, opts)
165
- notifier.call(ArgumentError.new('foo'), env: env)
166
- end
167
-
168
- private
169
-
170
- def notifier
171
- ExceptionNotifier::MattermostNotifier.new(webhook_url: URL)
172
- end
173
-
174
- def default_body
175
- {
176
- text: [
177
- '@channel',
178
- error_occurred_in,
179
- 'An *ArgumentError* occurred.',
180
- '*foo*'
181
- ].join("\n"),
182
- username: 'Exception Notifier'
183
- }
184
- end
185
-
186
- def default_headers
187
- { 'Content-Type' => 'application/json' }
188
- end
189
-
190
- def test_env
191
- Rack::MockRequest.env_for(
192
- '/',
193
- 'HTTP_HOST' => 'test.address',
194
- 'REMOTE_ADDR' => '127.0.0.1',
195
- 'HTTP_USER_AGENT' => 'Rails Testing',
196
- params: { id: 'foo' }
197
- )
198
- end
199
-
200
- def error_occurred_in
201
- if defined?(::Rails) && ::Rails.respond_to?(:env)
202
- '### ⚠️ Error occurred in test ⚠️'
203
- else
204
- '### ⚠️ Error occurred ⚠️'
205
- end
206
- end
207
-
208
- def github_link
209
- if defined?(::Rails) && ::Rails.respond_to?(:application)
210
- '[Create an issue]' \
211
- '(github.com/aschen/dummy/issues/new/?issue%5Btitle%5D=%5BBUG%5D+Error+500+%3A++%28ArgumentError%29+foo)'
212
- else
213
- # TODO: fix missing app name
214
- '[Create an issue]' \
215
- '(github.com/aschen//issues/new/?issue%5Btitle%5D=%5BBUG%5D+Error+500+%3A++%28ArgumentError%29+foo)'
216
- end
217
- end
218
-
219
- def request_info
220
- [
221
- '### Request',
222
- '```',
223
- '* url : http://test.address/?id=foo',
224
- '* http_method : GET',
225
- '* ip_address : 127.0.0.1',
226
- '* parameters : {"id"=>"foo"}',
227
- '* timestamp : 2018-12-09 12:07:16 UTC',
228
- '```'
229
- ]
230
- end
231
-
232
- def backtrace_info
233
- [
234
- '### Backtrace',
235
- '```',
236
- "* app/controllers/my_controller.rb:53:in `my_controller_params'",
237
- "* app/controllers/my_controller.rb:34:in `update'",
238
- '```'
239
- ]
240
- end
241
-
242
- def exception_data_info
243
- [
244
- '### Data',
245
- '```',
246
- '* foo : bar',
247
- '* john : doe',
248
- '```'
249
- ]
250
- end
251
- end
@@ -1,167 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'test_helper'
4
-
5
- class ErrorGroupTest < ActiveSupport::TestCase
6
- setup do
7
- module TestModule
8
- include ExceptionNotifier::ErrorGrouping
9
- @@error_grouping_cache = ActiveSupport::Cache::FileStore.new('test/dummy/tmp/non_default_location')
10
- end
11
-
12
- @exception = RuntimeError.new('ERROR')
13
- @exception.stubs(:backtrace).returns(['/path/where/error/raised:1'])
14
-
15
- @exception2 = RuntimeError.new('ERROR2')
16
- @exception2.stubs(:backtrace).returns(['/path/where/error/found:2'])
17
- end
18
-
19
- teardown do
20
- TestModule.error_grouping_cache.clear
21
- TestModule.fallback_cache_store.clear
22
- end
23
-
24
- test 'should add additional option: error_grouping' do
25
- assert_respond_to TestModule, :error_grouping
26
- assert_respond_to TestModule, :error_grouping=
27
- end
28
-
29
- test 'should set error_grouping to false default' do
30
- assert_equal false, TestModule.error_grouping
31
- end
32
-
33
- test 'should add additional option: error_grouping_cache' do
34
- assert_respond_to TestModule, :error_grouping_cache
35
- assert_respond_to TestModule, :error_grouping_cache=
36
- end
37
-
38
- test 'should add additional option: error_grouping_period' do
39
- assert_respond_to TestModule, :error_grouping_period
40
- assert_respond_to TestModule, :error_grouping_period=
41
- end
42
-
43
- test 'shoud set error_grouping_period to 5.minutes default' do
44
- assert_equal 300, TestModule.error_grouping_period
45
- end
46
-
47
- test 'should add additional option: notification_trigger' do
48
- assert_respond_to TestModule, :notification_trigger
49
- assert_respond_to TestModule, :notification_trigger=
50
- end
51
-
52
- test 'should return errors count nil when not same error for .error_count' do
53
- assert_nil TestModule.error_count('something')
54
- end
55
-
56
- test 'should return errors count when same error for .error_count' do
57
- TestModule.error_grouping_cache.write('error_key', 13)
58
- assert_equal 13, TestModule.error_count('error_key')
59
- end
60
-
61
- test 'should fallback to memory store cache if specified cache store failed to read' do
62
- TestModule.error_grouping_cache.stubs(:read).raises(RuntimeError.new('Failed to read'))
63
- original_fallback = TestModule.fallback_cache_store
64
- TestModule.expects(:fallback_cache_store).returns(original_fallback).at_least_once
65
-
66
- assert_nil TestModule.error_count('something_to_read')
67
- end
68
-
69
- test 'should save error with count for .save_error_count' do
70
- count = rand(1..10)
71
-
72
- TestModule.save_error_count('error_key', count)
73
- assert_equal count, TestModule.error_grouping_cache.read('error_key')
74
- end
75
-
76
- test 'should fallback to memory store cache if specified cache store failed to write' do
77
- TestModule.error_grouping_cache.stubs(:write).raises(RuntimeError.new('Failed to write'))
78
- original_fallback = TestModule.fallback_cache_store
79
- TestModule.expects(:fallback_cache_store).returns(original_fallback).at_least_once
80
-
81
- assert TestModule.save_error_count('something_to_cache', rand(1..10))
82
- end
83
-
84
- test 'should save accumulated_errors_count into options' do
85
- options = {}
86
- TestModule.group_error!(@exception, options)
87
-
88
- assert_equal 1, options[:accumulated_errors_count]
89
- end
90
-
91
- test 'should not group error if different exception in .group_error!' do
92
- options1 = {}
93
- TestModule.expects(:save_error_count).with { |key, count| key.is_a?(String) && count == 1 }.times(4).returns(true)
94
- TestModule.group_error!(@exception, options1)
95
-
96
- options2 = {}
97
- TestModule.group_error!(NoMethodError.new('method not found'), options2)
98
-
99
- assert_equal 1, options1[:accumulated_errors_count]
100
- assert_equal 1, options2[:accumulated_errors_count]
101
- end
102
-
103
- test 'should not group error is same exception but different message or backtrace' do
104
- options1 = {}
105
- TestModule.expects(:save_error_count).with { |key, count| key.is_a?(String) && count == 1 }.times(4).returns(true)
106
- TestModule.group_error!(@exception, options1)
107
-
108
- options2 = {}
109
- TestModule.group_error!(@exception2, options2)
110
-
111
- assert_equal 1, options1[:accumulated_errors_count]
112
- assert_equal 1, options2[:accumulated_errors_count]
113
- end
114
-
115
- test 'should group error if same exception and message' do
116
- options = {}
117
-
118
- 10.times do |i|
119
- @exception2.stubs(:backtrace).returns(["/path:#{i}"])
120
- TestModule.group_error!(@exception2, options)
121
- end
122
-
123
- assert_equal 10, options[:accumulated_errors_count]
124
- end
125
-
126
- test 'should group error if same exception and backtrace' do
127
- options = {}
128
-
129
- 10.times do |i|
130
- @exception2.stubs(:message).returns("ERRORS#{i}")
131
- TestModule.group_error!(@exception2, options)
132
- end
133
-
134
- assert_equal 10, options[:accumulated_errors_count]
135
- end
136
-
137
- test 'should group error by that message have high priority' do
138
- message_based_key = "exception:#{Zlib.crc32("RuntimeError\nmessage:ERROR")}"
139
- backtrace_based_key = "exception:#{Zlib.crc32("RuntimeError\npath:/path/where/error/raised:1")}"
140
-
141
- TestModule.save_error_count(message_based_key, 1)
142
- TestModule.save_error_count(backtrace_based_key, 1)
143
-
144
- TestModule.expects(:save_error_count).with(message_based_key, 2).once
145
- TestModule.expects(:save_error_count).with(backtrace_based_key, 2).never
146
-
147
- TestModule.group_error!(@exception, {})
148
- end
149
-
150
- test 'use default formula if not specify notification_trigger in .send_notification?' do
151
- TestModule.stubs(:notification_trigger).returns(nil)
152
-
153
- count = 16
154
- Math.expects(:log2).with(count).returns(4)
155
-
156
- assert TestModule.send_notification?(@exception, count)
157
- end
158
-
159
- test 'use specified trigger in .send_notification?' do
160
- trigger = proc { |_exception, count| (count % 4).zero? }
161
- TestModule.stubs(:notification_trigger).returns(trigger)
162
-
163
- count = 16
164
- trigger.expects(:call).with(@exception, count).returns(true)
165
- assert TestModule.send_notification?(@exception, count)
166
- end
167
- end
@@ -1,152 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'test_helper'
4
- require 'timecop'
5
-
6
- class FormatterTest < ActiveSupport::TestCase
7
- setup do
8
- @exception = RuntimeError.new('test')
9
- Timecop.freeze('2018-12-09 12:07:16 UTC')
10
- end
11
-
12
- teardown do
13
- Timecop.return
14
- end
15
-
16
- #
17
- # #title
18
- #
19
- test 'title returns correct content' do
20
- formatter = ExceptionNotifier::Formatter.new(@exception)
21
-
22
- title = if defined?(::Rails) && ::Rails.respond_to?(:env)
23
- '⚠️ Error occurred in test ⚠️'
24
- else
25
- '⚠️ Error occurred ⚠️'
26
- end
27
-
28
- assert_equal title, formatter.title
29
- end
30
-
31
- #
32
- # #subtitle
33
- #
34
- test 'subtitle without accumulated error' do
35
- formatter = ExceptionNotifier::Formatter.new(@exception)
36
- assert_equal 'A *RuntimeError* occurred.', formatter.subtitle
37
- end
38
-
39
- test 'subtitle with accumulated error' do
40
- formatter = ExceptionNotifier::Formatter.new(@exception, accumulated_errors_count: 3)
41
- assert_equal '3 *RuntimeError* occurred.', formatter.subtitle
42
- end
43
-
44
- test 'subtitle with controller' do
45
- env = Rack::MockRequest.env_for(
46
- '/', 'action_controller.instance' => test_controller
47
- )
48
-
49
- formatter = ExceptionNotifier::Formatter.new(@exception, env: env)
50
- assert_equal 'A *RuntimeError* occurred in *home#index*.', formatter.subtitle
51
- end
52
-
53
- #
54
- # #app_name
55
- #
56
- test 'app_name defaults to Rails app name' do
57
- formatter = ExceptionNotifier::Formatter.new(@exception)
58
-
59
- if defined?(::Rails) && ::Rails.respond_to?(:application)
60
- assert_equal 'dummy', formatter.app_name
61
- else
62
- assert_nil formatter.app_name
63
- end
64
- end
65
-
66
- test 'app_name can be overwritten using options' do
67
- formatter = ExceptionNotifier::Formatter.new(@exception, app_name: 'test')
68
- assert_equal 'test', formatter.app_name
69
- end
70
-
71
- #
72
- # #request_message
73
- #
74
- test 'request_message when env set' do
75
- text = [
76
- '```',
77
- '* url : http://test.address/?id=foo',
78
- '* http_method : GET',
79
- '* ip_address : 127.0.0.1',
80
- '* parameters : {"id"=>"foo"}',
81
- '* timestamp : 2018-12-09 12:07:16 UTC',
82
- '```'
83
- ].join("\n")
84
-
85
- env = Rack::MockRequest.env_for(
86
- '/',
87
- 'HTTP_HOST' => 'test.address',
88
- 'REMOTE_ADDR' => '127.0.0.1',
89
- params: { id: 'foo' }
90
- )
91
-
92
- formatter = ExceptionNotifier::Formatter.new(@exception, env: env)
93
- assert_equal text, formatter.request_message
94
- end
95
-
96
- test 'request_message when env not set' do
97
- formatter = ExceptionNotifier::Formatter.new(@exception)
98
- assert_nil formatter.request_message
99
- end
100
-
101
- #
102
- # #backtrace_message
103
- #
104
- test 'backtrace_message when backtrace set' do
105
- text = [
106
- '```',
107
- "* app/controllers/my_controller.rb:53:in `my_controller_params'",
108
- "* app/controllers/my_controller.rb:34:in `update'",
109
- '```'
110
- ].join("\n")
111
-
112
- @exception.set_backtrace([
113
- "app/controllers/my_controller.rb:53:in `my_controller_params'",
114
- "app/controllers/my_controller.rb:34:in `update'"
115
- ])
116
-
117
- formatter = ExceptionNotifier::Formatter.new(@exception)
118
- assert_equal text, formatter.backtrace_message
119
- end
120
-
121
- test 'backtrace_message when no backtrace' do
122
- formatter = ExceptionNotifier::Formatter.new(@exception)
123
- assert_nil formatter.backtrace_message
124
- end
125
-
126
- #
127
- # #controller_and_action
128
- #
129
- test 'correct controller_and_action if controller is present' do
130
- env = Rack::MockRequest.env_for(
131
- '/', 'action_controller.instance' => test_controller
132
- )
133
-
134
- formatter = ExceptionNotifier::Formatter.new(@exception, env: env)
135
- assert_equal 'home#index', formatter.controller_and_action
136
- end
137
-
138
- test 'controller_and_action is nil if no controller' do
139
- env = Rack::MockRequest.env_for('/')
140
-
141
- formatter = ExceptionNotifier::Formatter.new(@exception, env: env)
142
- assert_nil formatter.controller_and_action
143
- end
144
-
145
- def test_controller
146
- controller = mock('controller')
147
- controller.stubs(:action_name).returns('index')
148
- controller.stubs(:controller_name).returns('home')
149
-
150
- controller
151
- end
152
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'test_helper'
4
-
5
- # To allow sidekiq error handlers to be registered, sidekiq must be in
6
- # "server mode". This mode is triggered by loading sidekiq/cli. Note this
7
- # has to be loaded before exception_notification/sidekiq.
8
- require 'sidekiq/cli'
9
- require 'sidekiq/testing'
10
-
11
- require 'exception_notification/sidekiq'
12
-
13
- class MockSidekiqServer
14
- include ::Sidekiq::Component
15
-
16
- def config
17
- Sidekiq.default_configuration
18
- end
19
- end
20
-
21
- class SidekiqTest < ActiveSupport::TestCase
22
- test 'should call notify_exception when sidekiq raises an error' do
23
- server = MockSidekiqServer.new
24
- message = {}
25
- exception = RuntimeError.new
26
-
27
- ExceptionNotifier.expects(:notify_exception).with(
28
- exception,
29
- data: { sidekiq: message }
30
- )
31
-
32
- server.handle_exception(exception, message)
33
- end
34
- end