exception_notification 4.3.0 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/Appraisals +4 -2
- data/CHANGELOG.rdoc +47 -0
- data/CONTRIBUTING.md +18 -0
- data/Gemfile +3 -1
- data/README.md +97 -945
- data/Rakefile +4 -2
- data/docs/notifiers/campfire.md +50 -0
- data/docs/notifiers/custom.md +42 -0
- data/docs/notifiers/datadog.md +51 -0
- data/docs/notifiers/email.md +195 -0
- data/docs/notifiers/google_chat.md +31 -0
- data/docs/notifiers/hipchat.md +66 -0
- data/docs/notifiers/irc.md +97 -0
- data/docs/notifiers/mattermost.md +115 -0
- data/docs/notifiers/slack.md +161 -0
- data/docs/notifiers/sns.md +37 -0
- data/docs/notifiers/teams.md +54 -0
- data/docs/notifiers/webhook.md +60 -0
- data/examples/sample_app.rb +56 -0
- data/examples/sinatra/Gemfile +8 -6
- data/examples/sinatra/config.ru +3 -1
- data/examples/sinatra/sinatra_app.rb +19 -11
- data/exception_notification.gemspec +30 -24
- data/gemfiles/{rails4_0.gemfile → rails5_2.gemfile} +2 -2
- data/gemfiles/{rails4_1.gemfile → rails6_0.gemfile} +2 -2
- data/gemfiles/{rails4_2.gemfile → rails6_1.gemfile} +2 -2
- data/gemfiles/{rails5_0.gemfile → rails7_0.gemfile} +2 -2
- data/lib/exception_notification/rack.rb +28 -30
- data/lib/exception_notification/rails.rb +2 -0
- data/lib/exception_notification/resque.rb +10 -10
- data/lib/exception_notification/sidekiq.rb +10 -12
- data/lib/exception_notification/version.rb +5 -0
- data/lib/exception_notification.rb +3 -0
- data/lib/exception_notifier/base_notifier.rb +10 -5
- data/lib/exception_notifier/datadog_notifier.rb +156 -0
- data/lib/exception_notifier/email_notifier.rb +73 -88
- data/lib/exception_notifier/google_chat_notifier.rb +27 -119
- data/lib/exception_notifier/hipchat_notifier.rb +13 -12
- data/lib/exception_notifier/irc_notifier.rb +36 -33
- data/lib/exception_notifier/mattermost_notifier.rb +54 -137
- data/lib/exception_notifier/modules/backtrace_cleaner.rb +2 -2
- data/lib/exception_notifier/modules/error_grouping.rb +24 -13
- data/lib/exception_notifier/modules/formatter.rb +125 -0
- data/lib/exception_notifier/notifier.rb +9 -6
- data/lib/exception_notifier/slack_notifier.rb +65 -40
- data/lib/exception_notifier/sns_notifier.rb +23 -13
- data/lib/exception_notifier/teams_notifier.rb +67 -46
- data/lib/exception_notifier/views/exception_notifier/_backtrace.html.erb +1 -1
- data/lib/exception_notifier/views/exception_notifier/_environment.text.erb +1 -1
- data/lib/exception_notifier/views/exception_notifier/_request.text.erb +1 -1
- data/lib/exception_notifier/views/exception_notifier/exception_notification.html.erb +2 -2
- data/lib/exception_notifier/views/exception_notifier/exception_notification.text.erb +2 -2
- data/lib/exception_notifier/webhook_notifier.rb +17 -14
- data/lib/exception_notifier.rb +65 -10
- data/lib/generators/exception_notification/install_generator.rb +11 -5
- data/lib/generators/exception_notification/templates/{exception_notification.rb → exception_notification.rb.erb} +13 -11
- data/test/exception_notification/rack_test.rb +75 -13
- data/test/exception_notification/resque_test.rb +54 -0
- data/test/exception_notifier/datadog_notifier_test.rb +153 -0
- data/test/exception_notifier/email_notifier_test.rb +275 -153
- data/test/exception_notifier/google_chat_notifier_test.rb +158 -101
- data/test/exception_notifier/hipchat_notifier_test.rb +84 -81
- data/test/exception_notifier/irc_notifier_test.rb +36 -34
- data/test/exception_notifier/mattermost_notifier_test.rb +213 -67
- data/test/exception_notifier/modules/error_grouping_test.rb +41 -40
- data/test/exception_notifier/modules/formatter_test.rb +152 -0
- data/test/exception_notifier/sidekiq_test.rb +9 -17
- data/test/exception_notifier/slack_notifier_test.rb +66 -63
- data/test/exception_notifier/sns_notifier_test.rb +84 -32
- data/test/exception_notifier/teams_notifier_test.rb +25 -26
- data/test/exception_notifier/webhook_notifier_test.rb +52 -48
- data/test/exception_notifier_test.rb +150 -41
- data/test/support/exception_notifier_helper.rb +14 -0
- data/test/{dummy/app → support}/views/exception_notifier/_new_bkg_section.html.erb +0 -0
- data/test/{dummy/app → support}/views/exception_notifier/_new_bkg_section.text.erb +0 -0
- data/test/{dummy/app → support}/views/exception_notifier/_new_section.html.erb +0 -0
- data/test/{dummy/app → support}/views/exception_notifier/_new_section.text.erb +0 -0
- data/test/test_helper.rb +14 -13
- metadata +134 -175
- data/gemfiles/rails5_1.gemfile +0 -7
- data/lib/exception_notifier/campfire_notifier.rb +0 -40
- data/test/dummy/.gitignore +0 -4
- data/test/dummy/Rakefile +0 -7
- data/test/dummy/app/controllers/application_controller.rb +0 -3
- data/test/dummy/app/controllers/posts_controller.rb +0 -30
- data/test/dummy/app/helpers/application_helper.rb +0 -2
- data/test/dummy/app/helpers/posts_helper.rb +0 -2
- data/test/dummy/app/models/post.rb +0 -2
- data/test/dummy/app/views/layouts/application.html.erb +0 -14
- data/test/dummy/app/views/posts/_form.html.erb +0 -0
- data/test/dummy/app/views/posts/new.html.erb +0 -0
- data/test/dummy/app/views/posts/show.html.erb +0 -0
- data/test/dummy/config/application.rb +0 -42
- data/test/dummy/config/boot.rb +0 -6
- data/test/dummy/config/database.yml +0 -22
- data/test/dummy/config/environment.rb +0 -17
- data/test/dummy/config/environments/development.rb +0 -25
- data/test/dummy/config/environments/production.rb +0 -50
- data/test/dummy/config/environments/test.rb +0 -35
- data/test/dummy/config/initializers/backtrace_silencers.rb +0 -7
- data/test/dummy/config/initializers/inflections.rb +0 -10
- data/test/dummy/config/initializers/mime_types.rb +0 -5
- data/test/dummy/config/initializers/secret_token.rb +0 -8
- data/test/dummy/config/initializers/session_store.rb +0 -8
- data/test/dummy/config/locales/en.yml +0 -5
- data/test/dummy/config/routes.rb +0 -3
- data/test/dummy/config.ru +0 -4
- data/test/dummy/db/migrate/20110729022608_create_posts.rb +0 -15
- data/test/dummy/db/schema.rb +0 -24
- data/test/dummy/db/seeds.rb +0 -7
- data/test/dummy/lib/tasks/.gitkeep +0 -0
- data/test/dummy/public/404.html +0 -26
- data/test/dummy/public/422.html +0 -26
- data/test/dummy/public/500.html +0 -26
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/public/images/rails.png +0 -0
- data/test/dummy/public/index.html +0 -239
- data/test/dummy/public/javascripts/application.js +0 -2
- data/test/dummy/public/javascripts/controls.js +0 -965
- data/test/dummy/public/javascripts/dragdrop.js +0 -974
- data/test/dummy/public/javascripts/effects.js +0 -1123
- data/test/dummy/public/javascripts/prototype.js +0 -6001
- data/test/dummy/public/javascripts/rails.js +0 -191
- data/test/dummy/public/robots.txt +0 -5
- data/test/dummy/public/stylesheets/.gitkeep +0 -0
- data/test/dummy/public/stylesheets/scaffold.css +0 -56
- data/test/dummy/script/rails +0 -6
- data/test/dummy/test/functional/posts_controller_test.rb +0 -237
- data/test/dummy/test/test_helper.rb +0 -7
- data/test/exception_notifier/campfire_notifier_test.rb +0 -120
|
@@ -0,0 +1,152 @@
|
|
|
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,38 +1,30 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
2
4
|
|
|
3
5
|
# To allow sidekiq error handlers to be registered, sidekiq must be in
|
|
4
6
|
# "server mode". This mode is triggered by loading sidekiq/cli. Note this
|
|
5
7
|
# has to be loaded before exception_notification/sidekiq.
|
|
6
|
-
require
|
|
8
|
+
require 'sidekiq/cli'
|
|
9
|
+
require 'sidekiq/testing'
|
|
7
10
|
|
|
8
|
-
require
|
|
11
|
+
require 'exception_notification/sidekiq'
|
|
9
12
|
|
|
10
13
|
class MockSidekiqServer
|
|
11
14
|
include ::Sidekiq::ExceptionHandler
|
|
12
15
|
end
|
|
13
16
|
|
|
14
17
|
class SidekiqTest < ActiveSupport::TestCase
|
|
15
|
-
|
|
16
|
-
@_original_sidekiq_logger = Sidekiq::Logging.logger
|
|
17
|
-
|
|
18
|
-
# Silence sidekiq warning to stdout
|
|
19
|
-
Sidekiq::Logging.logger = nil
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
test "should call notify_exception when sidekiq raises an error" do
|
|
18
|
+
test 'should call notify_exception when sidekiq raises an error' do
|
|
23
19
|
server = MockSidekiqServer.new
|
|
24
|
-
message =
|
|
20
|
+
message = {}
|
|
25
21
|
exception = RuntimeError.new
|
|
26
22
|
|
|
27
23
|
ExceptionNotifier.expects(:notify_exception).with(
|
|
28
24
|
exception,
|
|
29
|
-
:
|
|
25
|
+
data: { sidekiq: message }
|
|
30
26
|
)
|
|
31
27
|
|
|
32
28
|
server.handle_exception(exception, message)
|
|
33
29
|
end
|
|
34
|
-
|
|
35
|
-
teardown do
|
|
36
|
-
Sidekiq::Logging.logger = @_original_sidekiq_logger
|
|
37
|
-
end
|
|
38
30
|
end
|
|
@@ -1,18 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'test_helper'
|
|
2
4
|
require 'slack-notifier'
|
|
3
5
|
|
|
4
6
|
class SlackNotifierTest < ActiveSupport::TestCase
|
|
5
|
-
|
|
6
7
|
def setup
|
|
7
8
|
@exception = fake_exception
|
|
8
9
|
@exception.stubs(:backtrace).returns(fake_backtrace)
|
|
9
10
|
@exception.stubs(:message).returns('exception message')
|
|
11
|
+
ExceptionNotifier::SlackNotifier.any_instance.stubs(:clean_backtrace).returns(fake_cleaned_backtrace)
|
|
10
12
|
Socket.stubs(:gethostname).returns('example.com')
|
|
11
13
|
end
|
|
12
14
|
|
|
13
|
-
test
|
|
15
|
+
test 'should send a slack notification if properly configured' do
|
|
14
16
|
options = {
|
|
15
|
-
webhook_url:
|
|
17
|
+
webhook_url: 'http://slack.webhook.url'
|
|
16
18
|
}
|
|
17
19
|
|
|
18
20
|
Slack::Notifier.any_instance.expects(:ping).with('', fake_notification)
|
|
@@ -21,9 +23,9 @@ class SlackNotifierTest < ActiveSupport::TestCase
|
|
|
21
23
|
slack_notifier.call(@exception)
|
|
22
24
|
end
|
|
23
25
|
|
|
24
|
-
test
|
|
26
|
+
test 'should send a slack notification without backtrace info if properly configured' do
|
|
25
27
|
options = {
|
|
26
|
-
webhook_url:
|
|
28
|
+
webhook_url: 'http://slack.webhook.url'
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
Slack::Notifier.any_instance.expects(:ping).with('', fake_notification(fake_exception_without_backtrace))
|
|
@@ -32,10 +34,10 @@ class SlackNotifierTest < ActiveSupport::TestCase
|
|
|
32
34
|
slack_notifier.call(fake_exception_without_backtrace)
|
|
33
35
|
end
|
|
34
36
|
|
|
35
|
-
test
|
|
37
|
+
test 'should send the notification to the specified channel' do
|
|
36
38
|
options = {
|
|
37
|
-
webhook_url:
|
|
38
|
-
channel:
|
|
39
|
+
webhook_url: 'http://slack.webhook.url',
|
|
40
|
+
channel: 'channel'
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
Slack::Notifier.any_instance.expects(:ping).with('', fake_notification)
|
|
@@ -47,10 +49,10 @@ class SlackNotifierTest < ActiveSupport::TestCase
|
|
|
47
49
|
assert_equal channel, options[:channel]
|
|
48
50
|
end
|
|
49
51
|
|
|
50
|
-
test
|
|
52
|
+
test 'should send the notification to the specified username' do
|
|
51
53
|
options = {
|
|
52
|
-
webhook_url:
|
|
53
|
-
username:
|
|
54
|
+
webhook_url: 'http://slack.webhook.url',
|
|
55
|
+
username: 'username'
|
|
54
56
|
}
|
|
55
57
|
|
|
56
58
|
Slack::Notifier.any_instance.expects(:ping).with('', fake_notification)
|
|
@@ -62,9 +64,9 @@ class SlackNotifierTest < ActiveSupport::TestCase
|
|
|
62
64
|
assert_equal username, options[:username]
|
|
63
65
|
end
|
|
64
66
|
|
|
65
|
-
test
|
|
67
|
+
test 'should send the notification with specific backtrace lines' do
|
|
66
68
|
options = {
|
|
67
|
-
webhook_url:
|
|
69
|
+
webhook_url: 'http://slack.webhook.url',
|
|
68
70
|
backtrace_lines: 1
|
|
69
71
|
}
|
|
70
72
|
|
|
@@ -74,10 +76,10 @@ class SlackNotifierTest < ActiveSupport::TestCase
|
|
|
74
76
|
slack_notifier.call(@exception)
|
|
75
77
|
end
|
|
76
78
|
|
|
77
|
-
test
|
|
78
|
-
field = {title:
|
|
79
|
+
test 'should send the notification with additional fields' do
|
|
80
|
+
field = { title: 'Branch', value: 'master', short: true }
|
|
79
81
|
options = {
|
|
80
|
-
webhook_url:
|
|
82
|
+
webhook_url: 'http://slack.webhook.url',
|
|
81
83
|
additional_fields: [field]
|
|
82
84
|
}
|
|
83
85
|
|
|
@@ -90,17 +92,17 @@ class SlackNotifierTest < ActiveSupport::TestCase
|
|
|
90
92
|
assert_equal additional_fields, options[:additional_fields]
|
|
91
93
|
end
|
|
92
94
|
|
|
93
|
-
test
|
|
95
|
+
test 'should pass the additional parameters to Slack::Notifier.ping' do
|
|
94
96
|
options = {
|
|
95
|
-
webhook_url:
|
|
96
|
-
username:
|
|
97
|
-
custom_hook:
|
|
97
|
+
webhook_url: 'http://slack.webhook.url',
|
|
98
|
+
username: 'test',
|
|
99
|
+
custom_hook: 'hook',
|
|
98
100
|
additional_parameters: {
|
|
99
|
-
icon_url:
|
|
101
|
+
icon_url: 'icon'
|
|
100
102
|
}
|
|
101
103
|
}
|
|
102
104
|
|
|
103
|
-
Slack::Notifier.any_instance.expects(:ping).with('', options[:additional_parameters].merge(fake_notification)
|
|
105
|
+
Slack::Notifier.any_instance.expects(:ping).with('', options[:additional_parameters].merge(fake_notification))
|
|
104
106
|
|
|
105
107
|
slack_notifier = ExceptionNotifier::SlackNotifier.new(options)
|
|
106
108
|
slack_notifier.call(@exception)
|
|
@@ -115,55 +117,57 @@ class SlackNotifierTest < ActiveSupport::TestCase
|
|
|
115
117
|
assert_nil slack_notifier.call(@exception)
|
|
116
118
|
end
|
|
117
119
|
|
|
118
|
-
test
|
|
120
|
+
test 'should pass along environment data' do
|
|
119
121
|
options = {
|
|
120
|
-
webhook_url:
|
|
121
|
-
ignore_data_if: lambda {|k,v|
|
|
122
|
-
|
|
122
|
+
webhook_url: 'http://slack.webhook.url',
|
|
123
|
+
ignore_data_if: lambda { |k, v|
|
|
124
|
+
k.to_s == 'key_to_be_ignored' || v.is_a?(Hash)
|
|
123
125
|
}
|
|
124
126
|
}
|
|
125
127
|
|
|
126
128
|
notification_options = {
|
|
127
129
|
env: {
|
|
128
|
-
'exception_notifier.exception_data' => {foo: 'bar', john: 'doe'}
|
|
130
|
+
'exception_notifier.exception_data' => { foo: 'bar', john: 'doe' }
|
|
129
131
|
},
|
|
130
132
|
data: {
|
|
131
|
-
'user_id'
|
|
133
|
+
'user_id' => 5,
|
|
132
134
|
'key_to_be_ignored' => 'whatever',
|
|
133
|
-
'ignore_as_well'
|
|
135
|
+
'ignore_as_well' => { what: 'ever' }
|
|
134
136
|
}
|
|
135
137
|
}
|
|
136
138
|
|
|
137
139
|
expected_data_string = "foo: bar\njohn: doe\nuser_id: 5"
|
|
138
140
|
|
|
139
|
-
Slack::Notifier.any_instance
|
|
141
|
+
Slack::Notifier.any_instance
|
|
142
|
+
.expects(:ping)
|
|
143
|
+
.with('', fake_notification(@exception, notification_options, expected_data_string))
|
|
140
144
|
slack_notifier = ExceptionNotifier::SlackNotifier.new(options)
|
|
141
145
|
slack_notifier.call(@exception, notification_options)
|
|
142
146
|
end
|
|
143
147
|
|
|
144
|
-
test
|
|
148
|
+
test 'should call pre/post_callback proc if specified' do
|
|
145
149
|
post_callback_called = 0
|
|
146
150
|
options = {
|
|
147
|
-
webhook_url:
|
|
148
|
-
username:
|
|
149
|
-
custom_hook:
|
|
150
|
-
:
|
|
151
|
-
(message_opts[:attachments] = []) << { text:
|
|
151
|
+
webhook_url: 'http://slack.webhook.url',
|
|
152
|
+
username: 'test',
|
|
153
|
+
custom_hook: 'hook',
|
|
154
|
+
pre_callback: proc { |_opts, _notifier, backtrace, _message, message_opts|
|
|
155
|
+
(message_opts[:attachments] = []) << { text: backtrace.join("\n").to_s, color: 'danger' }
|
|
152
156
|
},
|
|
153
|
-
:
|
|
157
|
+
post_callback: proc { |_opts, _notifier, _backtrace, _message, _message_opts|
|
|
154
158
|
post_callback_called = 1
|
|
155
159
|
},
|
|
156
160
|
additional_parameters: {
|
|
157
|
-
icon_url:
|
|
161
|
+
icon_url: 'icon'
|
|
158
162
|
}
|
|
159
163
|
}
|
|
160
164
|
|
|
161
165
|
Slack::Notifier.any_instance.expects(:ping).with('',
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
]
|
|
166
|
+
icon_url: 'icon',
|
|
167
|
+
attachments: [{
|
|
168
|
+
text: fake_backtrace.join("\n"),
|
|
169
|
+
color: 'danger'
|
|
170
|
+
}])
|
|
167
171
|
|
|
168
172
|
slack_notifier = ExceptionNotifier::SlackNotifier.new(options)
|
|
169
173
|
slack_notifier.call(@exception)
|
|
@@ -173,11 +177,9 @@ class SlackNotifierTest < ActiveSupport::TestCase
|
|
|
173
177
|
private
|
|
174
178
|
|
|
175
179
|
def fake_exception
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
e
|
|
180
|
-
end
|
|
180
|
+
5 / 0
|
|
181
|
+
rescue StandardError => e
|
|
182
|
+
e
|
|
181
183
|
end
|
|
182
184
|
|
|
183
185
|
def fake_exception_without_backtrace
|
|
@@ -186,17 +188,19 @@ class SlackNotifierTest < ActiveSupport::TestCase
|
|
|
186
188
|
|
|
187
189
|
def fake_backtrace
|
|
188
190
|
[
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
"backtrace line 3",
|
|
192
|
-
"backtrace line 4",
|
|
193
|
-
"backtrace line 5",
|
|
194
|
-
"backtrace line 6",
|
|
191
|
+
'backtrace line 1', 'backtrace line 2', 'backtrace line 3',
|
|
192
|
+
'backtrace line 4', 'backtrace line 5', 'backtrace line 6'
|
|
195
193
|
]
|
|
196
194
|
end
|
|
197
195
|
|
|
198
|
-
def
|
|
199
|
-
|
|
196
|
+
def fake_cleaned_backtrace
|
|
197
|
+
fake_backtrace[2..-1]
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def fake_notification(exception = @exception, notification_options = {},
|
|
201
|
+
data_string = nil, expected_backtrace_lines = 10, additional_fields = [])
|
|
202
|
+
|
|
203
|
+
exception_name = "*#{exception.class.to_s =~ /^[aeiou]/i ? 'An' : 'A'}* `#{exception.class}`"
|
|
200
204
|
if notification_options[:env].nil?
|
|
201
205
|
text = "#{exception_name} *occured in background*"
|
|
202
206
|
else
|
|
@@ -211,16 +215,15 @@ class SlackNotifierTest < ActiveSupport::TestCase
|
|
|
211
215
|
|
|
212
216
|
text += "\n"
|
|
213
217
|
|
|
214
|
-
fields = [
|
|
215
|
-
fields.push(
|
|
218
|
+
fields = [{ title: 'Exception', value: exception.message }]
|
|
219
|
+
fields.push(title: 'Hostname', value: 'example.com')
|
|
216
220
|
if exception.backtrace
|
|
217
|
-
formatted_backtrace = "```#{
|
|
218
|
-
fields.push(
|
|
221
|
+
formatted_backtrace = "```#{fake_cleaned_backtrace.first(expected_backtrace_lines).join("\n")}```"
|
|
222
|
+
fields.push(title: 'Backtrace', value: formatted_backtrace)
|
|
219
223
|
end
|
|
220
|
-
fields.push(
|
|
224
|
+
fields.push(title: 'Data', value: "```#{data_string}```") if data_string
|
|
221
225
|
additional_fields.each { |f| fields.push(f) }
|
|
222
226
|
|
|
223
|
-
{ attachments: [
|
|
227
|
+
{ attachments: [color: 'danger', text: text, fields: fields, mrkdwn_in: %w[text fields]] }
|
|
224
228
|
end
|
|
225
|
-
|
|
226
229
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'test_helper'
|
|
2
4
|
require 'aws-sdk-sns'
|
|
3
5
|
|
|
@@ -12,7 +14,7 @@ class SnsNotifierTest < ActiveSupport::TestCase
|
|
|
12
14
|
secret_access_key: 'my-secret_access_key',
|
|
13
15
|
region: 'us-east',
|
|
14
16
|
topic_arn: 'topicARN',
|
|
15
|
-
sns_prefix: '[App Exception]'
|
|
17
|
+
sns_prefix: '[App Exception]'
|
|
16
18
|
}
|
|
17
19
|
Socket.stubs(:gethostname).returns('example.com')
|
|
18
20
|
end
|
|
@@ -60,53 +62,103 @@ class SnsNotifierTest < ActiveSupport::TestCase
|
|
|
60
62
|
|
|
61
63
|
test 'should send a sns notification in background' do
|
|
62
64
|
Aws::SNS::Client.any_instance.expects(:publish).with(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
subject:
|
|
70
|
-
|
|
65
|
+
topic_arn: 'topicARN',
|
|
66
|
+
message: "3 MyException occured in background\n" \
|
|
67
|
+
"Exception: undefined method 'method=' for Empty\n" \
|
|
68
|
+
"Hostname: example.com\n" \
|
|
69
|
+
"Data: {}\n" \
|
|
70
|
+
"Backtrace:\n#{fake_backtrace.join("\n")}\n",
|
|
71
|
+
subject: '[App Exception] - 3 MyException occurred'
|
|
72
|
+
)
|
|
71
73
|
|
|
72
74
|
sns_notifier = ExceptionNotifier::SnsNotifier.new(@options)
|
|
73
|
-
sns_notifier.call(@exception,
|
|
75
|
+
sns_notifier.call(@exception, accumulated_errors_count: 3)
|
|
74
76
|
end
|
|
75
77
|
|
|
76
78
|
test 'should send a sns notification with controller#action information' do
|
|
77
|
-
|
|
79
|
+
controller = mock('controller')
|
|
80
|
+
controller.stubs(:action_name).returns('index')
|
|
81
|
+
controller.stubs(:controller_name).returns('examples')
|
|
78
82
|
|
|
79
83
|
Aws::SNS::Client.any_instance.expects(:publish).with(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
subject:
|
|
88
|
-
|
|
84
|
+
topic_arn: 'topicARN',
|
|
85
|
+
message: 'A MyException occurred while GET </examples> ' \
|
|
86
|
+
"was processed by examples#index\n" \
|
|
87
|
+
"Exception: undefined method 'method=' for Empty\n" \
|
|
88
|
+
"Hostname: example.com\n" \
|
|
89
|
+
"Data: {}\n" \
|
|
90
|
+
"Backtrace:\n#{fake_backtrace.join("\n")}\n",
|
|
91
|
+
subject: '[App Exception] - A MyException occurred'
|
|
92
|
+
)
|
|
89
93
|
|
|
90
94
|
sns_notifier = ExceptionNotifier::SnsNotifier.new(@options)
|
|
91
95
|
sns_notifier.call(@exception,
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
96
|
+
env: {
|
|
97
|
+
'REQUEST_METHOD' => 'GET',
|
|
98
|
+
'REQUEST_URI' => '/examples',
|
|
99
|
+
'action_controller.instance' => controller
|
|
100
|
+
})
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
test 'should put data from env["exception_notifier.exception_data"] into text' do
|
|
104
|
+
controller = mock('controller')
|
|
105
|
+
controller.stubs(:action_name).returns('index')
|
|
106
|
+
controller.stubs(:controller_name).returns('examples')
|
|
107
|
+
|
|
108
|
+
Aws::SNS::Client.any_instance.expects(:publish).with(
|
|
109
|
+
topic_arn: 'topicARN',
|
|
110
|
+
message: 'A MyException occurred while GET </examples> ' \
|
|
111
|
+
"was processed by examples#index\n" \
|
|
112
|
+
"Exception: undefined method 'method=' for Empty\n" \
|
|
113
|
+
"Hostname: example.com\n" \
|
|
114
|
+
"Data: {:current_user=>12}\n" \
|
|
115
|
+
"Backtrace:\n#{fake_backtrace.join("\n")}\n",
|
|
116
|
+
subject: '[App Exception] - A MyException occurred'
|
|
97
117
|
)
|
|
118
|
+
|
|
119
|
+
sns_notifier = ExceptionNotifier::SnsNotifier.new(@options)
|
|
120
|
+
sns_notifier.call(@exception,
|
|
121
|
+
env: {
|
|
122
|
+
'REQUEST_METHOD' => 'GET',
|
|
123
|
+
'REQUEST_URI' => '/examples',
|
|
124
|
+
'action_controller.instance' => controller,
|
|
125
|
+
'exception_notifier.exception_data' => { current_user: 12 }
|
|
126
|
+
})
|
|
98
127
|
end
|
|
128
|
+
test 'should put optional data into text' do
|
|
129
|
+
controller = mock('controller')
|
|
130
|
+
controller.stubs(:action_name).returns('index')
|
|
131
|
+
controller.stubs(:controller_name).returns('examples')
|
|
99
132
|
|
|
100
|
-
|
|
133
|
+
Aws::SNS::Client.any_instance.expects(:publish).with(
|
|
134
|
+
topic_arn: 'topicARN',
|
|
135
|
+
message: 'A MyException occurred while GET </examples> ' \
|
|
136
|
+
"was processed by examples#index\n" \
|
|
137
|
+
"Exception: undefined method 'method=' for Empty\n" \
|
|
138
|
+
"Hostname: example.com\n" \
|
|
139
|
+
"Data: {:current_user=>12}\n" \
|
|
140
|
+
"Backtrace:\n#{fake_backtrace.join("\n")}\n",
|
|
141
|
+
subject: '[App Exception] - A MyException occurred'
|
|
142
|
+
)
|
|
101
143
|
|
|
102
|
-
|
|
144
|
+
sns_notifier = ExceptionNotifier::SnsNotifier.new(@options)
|
|
145
|
+
sns_notifier.call(@exception,
|
|
146
|
+
env: {
|
|
147
|
+
'REQUEST_METHOD' => 'GET',
|
|
148
|
+
'REQUEST_URI' => '/examples',
|
|
149
|
+
'action_controller.instance' => controller
|
|
150
|
+
},
|
|
151
|
+
data: {
|
|
152
|
+
current_user: 12
|
|
153
|
+
})
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
private
|
|
103
157
|
|
|
104
158
|
def fake_exception
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
e
|
|
109
|
-
end
|
|
159
|
+
1 / 0
|
|
160
|
+
rescue StandardError => e
|
|
161
|
+
e
|
|
110
162
|
end
|
|
111
163
|
|
|
112
164
|
def fake_exception_without_backtrace
|