exception_notification 4.6.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.rdoc +16 -0
- data/CONTRIBUTING.md +23 -51
- data/Gemfile +1 -1
- data/Gemfile.lock +27 -33
- data/README.md +65 -31
- data/Rakefile +14 -7
- data/exception_notification.gemspec +27 -30
- data/gemfiles/pinned_dependencies.gemfile +8 -0
- data/gemfiles/rails7_1.gemfile +5 -0
- data/gemfiles/rails7_2.gemfile +5 -0
- data/gemfiles/rails8_0.gemfile +5 -0
- data/lib/exception_notification/rack.rb +4 -4
- data/lib/exception_notification/rails.rb +2 -2
- data/lib/exception_notification/rake.rb +3 -7
- data/lib/exception_notification/resque.rb +2 -2
- data/lib/exception_notification/sidekiq.rb +8 -23
- data/lib/exception_notification/version.rb +1 -1
- data/lib/exception_notification.rb +3 -3
- data/lib/exception_notifier/datadog_notifier.rb +26 -26
- data/lib/exception_notifier/email_notifier.rb +34 -30
- data/lib/exception_notifier/google_chat_notifier.rb +9 -9
- data/lib/exception_notifier/hipchat_notifier.rb +12 -12
- data/lib/exception_notifier/irc_notifier.rb +6 -6
- data/lib/exception_notifier/mattermost_notifier.rb +13 -13
- data/lib/exception_notifier/modules/error_grouping.rb +5 -5
- data/lib/exception_notifier/modules/formatter.rb +12 -12
- data/lib/exception_notifier/notifier.rb +3 -3
- data/lib/exception_notifier/slack_notifier.rb +16 -16
- data/lib/exception_notifier/sns_notifier.rb +9 -9
- data/lib/exception_notifier/teams_notifier.rb +61 -57
- data/lib/exception_notifier/webhook_notifier.rb +3 -3
- data/lib/exception_notifier.rb +27 -26
- data/lib/generators/exception_notification/install_generator.rb +7 -7
- data/lib/generators/exception_notification/templates/exception_notification.rb.erb +26 -27
- data/test/exception_notification/rack_test.rb +14 -14
- data/test/exception_notification/rake_test.rb +13 -13
- data/test/exception_notification/resque_test.rb +14 -14
- data/test/exception_notifier/datadog_notifier_test.rb +47 -46
- data/test/exception_notifier/email_notifier_test.rb +89 -98
- data/test/exception_notifier/google_chat_notifier_test.rb +77 -77
- data/test/exception_notifier/hipchat_notifier_test.rb +76 -74
- data/test/exception_notifier/irc_notifier_test.rb +26 -26
- data/test/exception_notifier/mattermost_notifier_test.rb +77 -77
- data/test/exception_notifier/modules/error_grouping_test.rb +39 -39
- data/test/exception_notifier/modules/formatter_test.rb +51 -49
- data/test/exception_notifier/sidekiq_test.rb +17 -10
- data/test/exception_notifier/slack_notifier_test.rb +66 -67
- data/test/exception_notifier/sns_notifier_test.rb +73 -70
- data/test/exception_notifier/teams_notifier_test.rb +33 -33
- data/test/exception_notifier/webhook_notifier_test.rb +34 -34
- data/test/exception_notifier_test.rb +51 -41
- data/test/test_helper.rb +8 -11
- metadata +45 -85
- data/Appraisals +0 -9
- data/gemfiles/rails5_2.gemfile +0 -7
- data/gemfiles/rails6_0.gemfile +0 -7
- data/gemfiles/rails6_1.gemfile +0 -7
- data/gemfiles/rails7_0.gemfile +0 -7
@@ -1,22 +1,21 @@
|
|
1
1
|
# Move this require to your `config/application.rb` if you want to be notified from runner commands too.
|
2
|
-
require
|
3
|
-
require
|
2
|
+
require "exception_notification/rails"
|
3
|
+
require "exception_notification/rake"
|
4
4
|
<% if options.sidekiq? %>
|
5
|
-
require
|
6
|
-
|
7
|
-
|
8
|
-
require
|
9
|
-
require 'resque/failure/redis'
|
10
|
-
require 'exception_notification/resque'
|
5
|
+
require "exception_notification/sidekiq"<% end %><% if options.resque? %>
|
6
|
+
require "resque/failure/multiple"
|
7
|
+
require "resque/failure/redis"
|
8
|
+
require "exception_notification/resque"
|
11
9
|
|
12
|
-
Resque::Failure::Multiple.classes = [Resque::Failure::Redis, ExceptionNotification::Resque]
|
13
|
-
Resque::Failure.backend = Resque::Failure::Multiple
|
14
|
-
<% end %>
|
10
|
+
Resque::Failure::Multiple.classes = [ Resque::Failure::Redis, ExceptionNotification::Resque ]
|
11
|
+
Resque::Failure.backend = Resque::Failure::Multiple<% end %>
|
15
12
|
|
16
13
|
ExceptionNotification.configure do |config|
|
17
|
-
# Ignore additional exception types.
|
18
|
-
#
|
19
|
-
#
|
14
|
+
# Ignore additional exception types. The default list of exception classes is:
|
15
|
+
# ActiveRecord::RecordNotFound Mongoid::Errors::DocumentNotFound AbstractController::ActionNotFound
|
16
|
+
# ActionController::RoutingError ActionController::UnknownFormat ActionController::UrlGenerationError
|
17
|
+
# ActionDispatch::Http::MimeNegotiation::InvalidType Rack::Utils::InvalidParameterError
|
18
|
+
# config.ignored_exceptions += %w[ActionView::TemplateError CustomError]
|
20
19
|
|
21
20
|
# Adds a condition to decide when an exception must be ignored or not.
|
22
21
|
# The ignore_if method can be invoked multiple times to add extra conditions.
|
@@ -25,33 +24,33 @@ ExceptionNotification.configure do |config|
|
|
25
24
|
# end
|
26
25
|
|
27
26
|
# Ignore exceptions generated by crawlers
|
28
|
-
# config.ignore_crawlers %w
|
27
|
+
# config.ignore_crawlers %w[Googlebot bingbot]
|
29
28
|
|
30
29
|
# Notifiers =================================================================
|
31
30
|
|
32
31
|
# Email notifier sends notifications by email.
|
33
32
|
config.add_notifier :email, {
|
34
|
-
email_prefix:
|
35
|
-
sender_address: %
|
36
|
-
exception_recipients: %w
|
33
|
+
email_prefix: "[ERROR] ",
|
34
|
+
sender_address: %("Notifier" <notifier@example.com>),
|
35
|
+
exception_recipients: %w[exceptions@example.com]
|
37
36
|
}
|
38
37
|
|
39
|
-
# Campfire notifier sends notifications to your Campfire room. Requires
|
38
|
+
# Campfire notifier sends notifications to your Campfire room. Requires "tinder" gem.
|
40
39
|
# config.add_notifier :campfire, {
|
41
|
-
# subdomain:
|
42
|
-
# token:
|
43
|
-
# room_name:
|
40
|
+
# subdomain: "my_subdomain",
|
41
|
+
# token: "my_token",
|
42
|
+
# room_name: "my_room"
|
44
43
|
# }
|
45
44
|
|
46
|
-
# HipChat notifier sends notifications to your HipChat room. Requires
|
45
|
+
# HipChat notifier sends notifications to your HipChat room. Requires "hipchat" gem.
|
47
46
|
# config.add_notifier :hipchat, {
|
48
|
-
# api_token:
|
49
|
-
# room_name:
|
47
|
+
# api_token: "my_token",
|
48
|
+
# room_name: "my_room"
|
50
49
|
# }
|
51
50
|
|
52
|
-
# Webhook notifier sends notifications over HTTP protocol. Requires
|
51
|
+
# Webhook notifier sends notifications over HTTP protocol. Requires "httparty" gem.
|
53
52
|
# config.add_notifier :webhook, {
|
54
|
-
# url:
|
53
|
+
# url: "http://example.com:5555/hubot/path",
|
55
54
|
# http_method: :post
|
56
55
|
# }
|
57
56
|
end
|
@@ -1,11 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "test_helper"
|
4
4
|
|
5
5
|
class RackTest < ActiveSupport::TestCase
|
6
6
|
setup do
|
7
7
|
@pass_app = Object.new
|
8
|
-
@pass_app.stubs(:call).returns([nil, {
|
8
|
+
@pass_app.stubs(:call).returns([nil, {"X-Cascade" => "pass"}, nil])
|
9
9
|
|
10
10
|
@normal_app = Object.new
|
11
11
|
@normal_app.stubs(:call).returns([nil, {}, nil])
|
@@ -25,41 +25,41 @@ class RackTest < ActiveSupport::TestCase
|
|
25
25
|
ExceptionNotification::Rack.new(@pass_app, ignore_cascade_pass: false).call({})
|
26
26
|
end
|
27
27
|
|
28
|
-
test
|
28
|
+
test "should assign error_grouping if error_grouping is specified" do
|
29
29
|
refute ExceptionNotifier.error_grouping
|
30
30
|
ExceptionNotification::Rack.new(@normal_app, error_grouping: true).call({})
|
31
31
|
assert ExceptionNotifier.error_grouping
|
32
32
|
end
|
33
33
|
|
34
|
-
test
|
34
|
+
test "should assign notification_trigger if notification_trigger is specified" do
|
35
35
|
assert_nil ExceptionNotifier.notification_trigger
|
36
36
|
ExceptionNotification::Rack.new(@normal_app, notification_trigger: ->(_i) { true }).call({})
|
37
37
|
assert_respond_to ExceptionNotifier.notification_trigger, :call
|
38
38
|
end
|
39
39
|
|
40
40
|
if defined?(Rails) && Rails.respond_to?(:cache)
|
41
|
-
test
|
41
|
+
test "should set default cache to Rails cache" do
|
42
42
|
ExceptionNotification::Rack.new(@normal_app, error_grouping: true).call({})
|
43
43
|
assert_equal Rails.cache, ExceptionNotifier.error_grouping_cache
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
47
|
-
test
|
47
|
+
test "should ignore exceptions with Usar Agent in ignore_crawlers" do
|
48
48
|
exception_app = Object.new
|
49
49
|
exception_app.stubs(:call).raises(RuntimeError)
|
50
50
|
|
51
|
-
env = {
|
51
|
+
env = {"HTTP_USER_AGENT" => "Mozilla/5.0 (compatible; Crawlerbot/2.1;)"}
|
52
52
|
|
53
53
|
begin
|
54
54
|
ExceptionNotification::Rack.new(exception_app, ignore_crawlers: %w[Crawlerbot]).call(env)
|
55
55
|
|
56
56
|
flunk
|
57
|
-
rescue
|
58
|
-
refute env[
|
57
|
+
rescue
|
58
|
+
refute env["exception_notifier.delivered"]
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
|
-
test
|
62
|
+
test "should ignore exceptions if ignore_if condition is met" do
|
63
63
|
exception_app = Object.new
|
64
64
|
exception_app.stubs(:call).raises(RuntimeError)
|
65
65
|
|
@@ -72,12 +72,12 @@ class RackTest < ActiveSupport::TestCase
|
|
72
72
|
).call(env)
|
73
73
|
|
74
74
|
flunk
|
75
|
-
rescue
|
76
|
-
refute env[
|
75
|
+
rescue
|
76
|
+
refute env["exception_notifier.delivered"]
|
77
77
|
end
|
78
78
|
end
|
79
79
|
|
80
|
-
test
|
80
|
+
test "should ignore exceptions with notifiers that satisfies ignore_notifier_if condition" do
|
81
81
|
exception_app = Object.new
|
82
82
|
exception_app.stubs(:call).raises(RuntimeError)
|
83
83
|
|
@@ -98,7 +98,7 @@ class RackTest < ActiveSupport::TestCase
|
|
98
98
|
).call(env)
|
99
99
|
|
100
100
|
flunk
|
101
|
-
rescue
|
101
|
+
rescue
|
102
102
|
refute notifier1_called
|
103
103
|
assert notifier2_called
|
104
104
|
end
|
@@ -1,33 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "test_helper"
|
4
4
|
|
5
|
-
require
|
6
|
-
require
|
5
|
+
require "rake"
|
6
|
+
require "exception_notification/rake"
|
7
7
|
|
8
8
|
class RakeTest < ActiveSupport::TestCase
|
9
9
|
setup do
|
10
10
|
Rake::Task.define_task :dependency_1 do
|
11
|
-
puts
|
11
|
+
nil # noop but could puts for debugging
|
12
12
|
end
|
13
13
|
Rake::Task.define_task raise_exception: :dependency_1 do
|
14
|
-
raise
|
14
|
+
raise "test exception"
|
15
15
|
end
|
16
16
|
@task = Rake::Task[:raise_exception]
|
17
17
|
end
|
18
18
|
|
19
|
-
test
|
19
|
+
test "notifies of exception" do
|
20
20
|
ExceptionNotifier.expects(:notify_exception).with do |ex, opts|
|
21
21
|
data = opts[:data]
|
22
22
|
ex.is_a?(RuntimeError) &&
|
23
|
-
ex.message ==
|
24
|
-
data[:error_class] ==
|
25
|
-
data[:error_message] ==
|
26
|
-
data[:rake][:rake_command_line] ==
|
27
|
-
data[:rake][:name] ==
|
23
|
+
ex.message == "test exception" &&
|
24
|
+
data[:error_class] == "RuntimeError" &&
|
25
|
+
data[:error_message] == "test exception" &&
|
26
|
+
data[:rake][:rake_command_line] == "rake " &&
|
27
|
+
data[:rake][:name] == "raise_exception" &&
|
28
28
|
data[:rake][:timestamp] &&
|
29
|
-
data[:rake][:sources] == [
|
30
|
-
data[:rake][:prerequisite_tasks][0][:name] ==
|
29
|
+
data[:rake][:sources] == ["dependency_1"] &&
|
30
|
+
data[:rake][:prerequisite_tasks][0][:name] == "dependency_1"
|
31
31
|
end
|
32
32
|
|
33
33
|
# The original error is re-raised
|
@@ -1,12 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "test_helper"
|
4
4
|
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
5
|
+
require "exception_notification/resque"
|
6
|
+
require "resque"
|
7
|
+
require "mock_redis"
|
8
|
+
require "resque/failure/multiple"
|
9
|
+
require "resque/failure/redis"
|
10
10
|
|
11
11
|
class ResqueTest < ActiveSupport::TestCase
|
12
12
|
setup do
|
@@ -21,22 +21,22 @@ class ResqueTest < ActiveSupport::TestCase
|
|
21
21
|
@worker.cant_fork = true
|
22
22
|
end
|
23
23
|
|
24
|
-
test
|
24
|
+
test "count returns the number of failures" do
|
25
25
|
Resque::Job.create(:jobs, BadJob)
|
26
26
|
@worker.work(0)
|
27
27
|
assert_equal 1, ExceptionNotification::Resque.count
|
28
28
|
end
|
29
29
|
|
30
|
-
test
|
30
|
+
test "notifies exception when job fails" do
|
31
31
|
ExceptionNotifier.expects(:notify_exception).with do |ex, opts|
|
32
32
|
ex.is_a?(RuntimeError) &&
|
33
|
-
ex.message ==
|
34
|
-
opts[:data][:resque][:error_class] ==
|
35
|
-
opts[:data][:resque][:error_message] ==
|
33
|
+
ex.message == "Bad job!" &&
|
34
|
+
opts[:data][:resque][:error_class] == "RuntimeError" &&
|
35
|
+
opts[:data][:resque][:error_message] == "Bad job!" &&
|
36
36
|
opts[:data][:resque][:failed_at].present? &&
|
37
37
|
opts[:data][:resque][:payload] == {
|
38
|
-
|
39
|
-
|
38
|
+
"class" => "ResqueTest::BadJob",
|
39
|
+
"args" => []
|
40
40
|
} &&
|
41
41
|
opts[:data][:resque][:queue] == :jobs &&
|
42
42
|
opts[:data][:resque][:worker].present?
|
@@ -48,7 +48,7 @@ class ResqueTest < ActiveSupport::TestCase
|
|
48
48
|
|
49
49
|
class BadJob
|
50
50
|
def self.perform
|
51
|
-
raise
|
51
|
+
raise "Bad job!"
|
52
52
|
end
|
53
53
|
end
|
54
54
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
3
|
+
require "test_helper"
|
4
|
+
require "dogapi/common"
|
5
|
+
require "dogapi/event"
|
6
6
|
|
7
7
|
class DatadogNotifierTest < ActiveSupport::TestCase
|
8
8
|
def setup
|
@@ -16,7 +16,7 @@ class DatadogNotifierTest < ActiveSupport::TestCase
|
|
16
16
|
@request = FakeRequest.new
|
17
17
|
end
|
18
18
|
|
19
|
-
test
|
19
|
+
test "should send an event to datadog" do
|
20
20
|
fake_event = Dogapi::Event.any_instance
|
21
21
|
@client.expects(:emit_event).with(fake_event)
|
22
22
|
|
@@ -24,15 +24,15 @@ class DatadogNotifierTest < ActiveSupport::TestCase
|
|
24
24
|
@notifier.call(@exception)
|
25
25
|
end
|
26
26
|
|
27
|
-
test
|
27
|
+
test "should include exception class in event title" do
|
28
28
|
event = @notifier.datadog_event(@exception)
|
29
|
-
assert_includes event.msg_title,
|
29
|
+
assert_includes event.msg_title, "FakeException"
|
30
30
|
end
|
31
31
|
|
32
|
-
test
|
32
|
+
test "should include prefix in event title and not append previous events" do
|
33
33
|
options = {
|
34
34
|
client: @client,
|
35
|
-
title_prefix:
|
35
|
+
title_prefix: "prefix"
|
36
36
|
}
|
37
37
|
|
38
38
|
notifier = ExceptionNotifier::DatadogNotifier.new(options)
|
@@ -43,43 +43,43 @@ class DatadogNotifierTest < ActiveSupport::TestCase
|
|
43
43
|
assert_equal event2.msg_title, 'prefix (DatadogNotifierTest::FakeException) "Fake exception message"'
|
44
44
|
end
|
45
45
|
|
46
|
-
test
|
46
|
+
test "should include exception message in event title" do
|
47
47
|
event = @notifier.datadog_event(@exception)
|
48
|
-
assert_includes event.msg_title,
|
48
|
+
assert_includes event.msg_title, "Fake exception message"
|
49
49
|
end
|
50
50
|
|
51
|
-
test
|
51
|
+
test "should include controller info in event title if controller information is available" do
|
52
52
|
event = @notifier.datadog_event(@exception,
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
assert_includes event.msg_title,
|
59
|
-
assert_includes event.msg_title,
|
53
|
+
env: {
|
54
|
+
"action_controller.instance" => @controller,
|
55
|
+
"REQUEST_METHOD" => "GET",
|
56
|
+
"rack.input" => ""
|
57
|
+
})
|
58
|
+
assert_includes event.msg_title, "Fake controller"
|
59
|
+
assert_includes event.msg_title, "Fake action"
|
60
60
|
end
|
61
61
|
|
62
|
-
test
|
62
|
+
test "should include backtrace info in event body" do
|
63
63
|
event = @notifier.datadog_event(@exception)
|
64
64
|
assert_includes event.msg_text, "backtrace line 1\nbacktrace line 2\nbacktrace line 3"
|
65
65
|
end
|
66
66
|
|
67
|
-
test
|
67
|
+
test "should include request info in event body" do
|
68
68
|
ActionDispatch::Request.stubs(:new).returns(@request)
|
69
69
|
|
70
70
|
event = @notifier.datadog_event(@exception,
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
assert_includes event.msg_text,
|
77
|
-
assert_includes event.msg_text,
|
78
|
-
assert_includes event.msg_text,
|
79
|
-
assert_includes event.msg_text,
|
71
|
+
env: {
|
72
|
+
"action_controller.instance" => @controller,
|
73
|
+
"REQUEST_METHOD" => "GET",
|
74
|
+
"rack.input" => ""
|
75
|
+
})
|
76
|
+
assert_includes event.msg_text, "http://localhost:8080"
|
77
|
+
assert_includes event.msg_text, "GET"
|
78
|
+
assert_includes event.msg_text, "127.0.0.1"
|
79
|
+
assert_includes event.msg_text, {"param 1" => "value 1", "param 2" => "value 2"}.to_s
|
80
80
|
end
|
81
81
|
|
82
|
-
test
|
82
|
+
test "should include tags in event" do
|
83
83
|
options = {
|
84
84
|
client: @client,
|
85
85
|
tags: %w[error production]
|
@@ -89,64 +89,65 @@ class DatadogNotifierTest < ActiveSupport::TestCase
|
|
89
89
|
assert_equal event.tags, %w[error production]
|
90
90
|
end
|
91
91
|
|
92
|
-
test
|
92
|
+
test "should include event title in event aggregation key" do
|
93
93
|
event = @notifier.datadog_event(@exception)
|
94
94
|
assert_equal event.aggregation_key, [event.msg_title]
|
95
95
|
end
|
96
96
|
|
97
97
|
class FakeDatadogClient
|
98
|
-
def emit_event(event)
|
98
|
+
def emit_event(event)
|
99
|
+
end
|
99
100
|
end
|
100
101
|
|
101
102
|
class FakeController
|
102
103
|
def controller_name
|
103
|
-
|
104
|
+
"Fake controller"
|
104
105
|
end
|
105
106
|
|
106
107
|
def action_name
|
107
|
-
|
108
|
+
"Fake action"
|
108
109
|
end
|
109
110
|
end
|
110
111
|
|
111
112
|
class FakeException
|
112
113
|
def backtrace
|
113
114
|
[
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
115
|
+
"backtrace line 1",
|
116
|
+
"backtrace line 2",
|
117
|
+
"backtrace line 3",
|
118
|
+
"backtrace line 4",
|
119
|
+
"backtrace line 5"
|
119
120
|
]
|
120
121
|
end
|
121
122
|
|
122
123
|
def message
|
123
|
-
|
124
|
+
"Fake exception message"
|
124
125
|
end
|
125
126
|
end
|
126
127
|
|
127
128
|
class FakeRequest
|
128
129
|
def url
|
129
|
-
|
130
|
+
"http://localhost:8080"
|
130
131
|
end
|
131
132
|
|
132
133
|
def request_method
|
133
|
-
|
134
|
+
"GET"
|
134
135
|
end
|
135
136
|
|
136
137
|
def remote_ip
|
137
|
-
|
138
|
+
"127.0.0.1"
|
138
139
|
end
|
139
140
|
|
140
141
|
def filtered_parameters
|
141
142
|
{
|
142
|
-
|
143
|
-
|
143
|
+
"param 1" => "value 1",
|
144
|
+
"param 2" => "value 2"
|
144
145
|
}
|
145
146
|
end
|
146
147
|
|
147
148
|
def session
|
148
149
|
{
|
149
|
-
|
150
|
+
"session_id" => "1234"
|
150
151
|
}
|
151
152
|
end
|
152
153
|
end
|