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
|
@@ -1,15 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'action_dispatch'
|
|
2
4
|
require 'active_support/core_ext/time'
|
|
3
5
|
|
|
4
6
|
module ExceptionNotifier
|
|
5
7
|
class WebhookNotifier < BaseNotifier
|
|
6
|
-
|
|
7
8
|
def initialize(options)
|
|
8
9
|
super
|
|
9
10
|
@default_options = options
|
|
10
11
|
end
|
|
11
12
|
|
|
12
|
-
def call(exception, options={})
|
|
13
|
+
def call(exception, options = {})
|
|
13
14
|
env = options[:env]
|
|
14
15
|
|
|
15
16
|
options = options.reverse_merge(@default_options)
|
|
@@ -18,23 +19,25 @@ module ExceptionNotifier
|
|
|
18
19
|
|
|
19
20
|
options[:body] ||= {}
|
|
20
21
|
options[:body][:server] = Socket.gethostname
|
|
21
|
-
options[:body][:process] =
|
|
22
|
-
if defined?(Rails) && Rails.respond_to?(:root)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
options[:body][:process] = $PROCESS_ID
|
|
23
|
+
options[:body][:rails_root] = Rails.root if defined?(Rails) && Rails.respond_to?(:root)
|
|
24
|
+
options[:body][:exception] = {
|
|
25
|
+
error_class: exception.class.to_s,
|
|
26
|
+
message: exception.message.inspect,
|
|
27
|
+
backtrace: exception.backtrace
|
|
28
|
+
}
|
|
28
29
|
options[:body][:data] = (env && env['exception_notifier.exception_data'] || {}).merge(options[:data] || {})
|
|
29
30
|
|
|
30
31
|
unless env.nil?
|
|
31
32
|
request = ActionDispatch::Request.new(env)
|
|
32
33
|
|
|
33
|
-
request_items = {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
request_items = {
|
|
35
|
+
url: request.original_url,
|
|
36
|
+
http_method: request.method,
|
|
37
|
+
ip_address: request.remote_ip,
|
|
38
|
+
parameters: request.filtered_parameters,
|
|
39
|
+
timestamp: Time.current
|
|
40
|
+
}
|
|
38
41
|
|
|
39
42
|
options[:body][:request] = request_items
|
|
40
43
|
options[:body][:session] = request.session
|
data/lib/exception_notifier.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'logger'
|
|
2
4
|
require 'active_support/core_ext/string/inflections'
|
|
3
5
|
require 'active_support/core_ext/module/attribute_accessors'
|
|
@@ -8,10 +10,10 @@ module ExceptionNotifier
|
|
|
8
10
|
include ErrorGrouping
|
|
9
11
|
|
|
10
12
|
autoload :BacktraceCleaner, 'exception_notifier/modules/backtrace_cleaner'
|
|
13
|
+
autoload :Formatter, 'exception_notifier/modules/formatter'
|
|
11
14
|
|
|
12
15
|
autoload :Notifier, 'exception_notifier/notifier'
|
|
13
16
|
autoload :EmailNotifier, 'exception_notifier/email_notifier'
|
|
14
|
-
autoload :CampfireNotifier, 'exception_notifier/campfire_notifier'
|
|
15
17
|
autoload :HipchatNotifier, 'exception_notifier/hipchat_notifier'
|
|
16
18
|
autoload :WebhookNotifier, 'exception_notifier/webhook_notifier'
|
|
17
19
|
autoload :IrcNotifier, 'exception_notifier/irc_notifier'
|
|
@@ -20,6 +22,7 @@ module ExceptionNotifier
|
|
|
20
22
|
autoload :TeamsNotifier, 'exception_notifier/teams_notifier'
|
|
21
23
|
autoload :SnsNotifier, 'exception_notifier/sns_notifier'
|
|
22
24
|
autoload :GoogleChatNotifier, 'exception_notifier/google_chat_notifier'
|
|
25
|
+
autoload :DatadogNotifier, 'exception_notifier/datadog_notifier'
|
|
23
26
|
|
|
24
27
|
class UndefinedNotifierError < StandardError; end
|
|
25
28
|
|
|
@@ -29,7 +32,10 @@ module ExceptionNotifier
|
|
|
29
32
|
|
|
30
33
|
# Define a set of exceptions to be ignored, ie, dont send notifications when any of them are raised.
|
|
31
34
|
mattr_accessor :ignored_exceptions
|
|
32
|
-
@@ignored_exceptions = %w
|
|
35
|
+
@@ignored_exceptions = %w[
|
|
36
|
+
ActiveRecord::RecordNotFound Mongoid::Errors::DocumentNotFound AbstractController::ActionNotFound
|
|
37
|
+
ActionController::RoutingError ActionController::UnknownFormat ActionController::UrlGenerationError
|
|
38
|
+
]
|
|
33
39
|
|
|
34
40
|
mattr_accessor :testing_mode
|
|
35
41
|
@@testing_mode = false
|
|
@@ -38,6 +44,9 @@ module ExceptionNotifier
|
|
|
38
44
|
# Store conditions that decide when exceptions must be ignored or not.
|
|
39
45
|
@@ignores = []
|
|
40
46
|
|
|
47
|
+
# Store by-notifier conditions that decide when exceptions must be ignored or not.
|
|
48
|
+
@@by_notifier_ignores = {}
|
|
49
|
+
|
|
41
50
|
# Store notifiers that send notifications when exceptions are raised.
|
|
42
51
|
@@notifiers = {}
|
|
43
52
|
|
|
@@ -45,19 +54,25 @@ module ExceptionNotifier
|
|
|
45
54
|
self.testing_mode = true
|
|
46
55
|
end
|
|
47
56
|
|
|
48
|
-
def notify_exception(exception, options={}, &block)
|
|
57
|
+
def notify_exception(exception, options = {}, &block)
|
|
49
58
|
return false if ignored_exception?(options[:ignore_exceptions], exception)
|
|
50
59
|
return false if ignored?(exception, options)
|
|
60
|
+
|
|
51
61
|
if error_grouping
|
|
52
62
|
errors_count = group_error!(exception, options)
|
|
53
63
|
return false unless send_notification?(exception, errors_count)
|
|
54
64
|
end
|
|
55
65
|
|
|
66
|
+
notification_fired = false
|
|
56
67
|
selected_notifiers = options.delete(:notifiers) || notifiers
|
|
57
68
|
[*selected_notifiers].each do |notifier|
|
|
58
|
-
|
|
69
|
+
unless notifier_ignored?(exception, options, notifier: notifier)
|
|
70
|
+
fire_notification(notifier, exception, options.dup, &block)
|
|
71
|
+
notification_fired = true
|
|
72
|
+
end
|
|
59
73
|
end
|
|
60
|
-
|
|
74
|
+
|
|
75
|
+
notification_fired
|
|
61
76
|
end
|
|
62
77
|
|
|
63
78
|
def register_exception_notifier(name, notifier_or_options)
|
|
@@ -92,23 +107,52 @@ module ExceptionNotifier
|
|
|
92
107
|
@@ignores << block
|
|
93
108
|
end
|
|
94
109
|
|
|
110
|
+
def ignore_notifier_if(notifier, &block)
|
|
111
|
+
@@by_notifier_ignores[notifier] = block
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def ignore_crawlers(crawlers)
|
|
115
|
+
ignore_if do |_exception, opts|
|
|
116
|
+
opts.key?(:env) && from_crawler(opts[:env], crawlers)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
95
120
|
def clear_ignore_conditions!
|
|
96
121
|
@@ignores.clear
|
|
122
|
+
@@by_notifier_ignores.clear
|
|
97
123
|
end
|
|
98
124
|
|
|
99
125
|
private
|
|
126
|
+
|
|
100
127
|
def ignored?(exception, options)
|
|
101
|
-
@@ignores.any?{ |condition| condition.call(exception, options) }
|
|
128
|
+
@@ignores.any? { |condition| condition.call(exception, options) }
|
|
102
129
|
rescue Exception => e
|
|
103
130
|
raise e if @@testing_mode
|
|
104
131
|
|
|
105
|
-
logger.warn
|
|
132
|
+
logger.warn(
|
|
133
|
+
"An error occurred when evaluating an ignore condition. #{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
|
|
134
|
+
)
|
|
135
|
+
false
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def notifier_ignored?(exception, options, notifier:)
|
|
139
|
+
return false unless @@by_notifier_ignores.key?(notifier)
|
|
140
|
+
|
|
141
|
+
condition = @@by_notifier_ignores[notifier]
|
|
142
|
+
condition.call(exception, options)
|
|
143
|
+
rescue Exception => e
|
|
144
|
+
raise e if @@testing_mode
|
|
145
|
+
|
|
146
|
+
logger.warn(<<~"MESSAGE")
|
|
147
|
+
An error occurred when evaluating a by-notifier ignore condition. #{e.class}: #{e.message}
|
|
148
|
+
#{e.backtrace.join("\n")}
|
|
149
|
+
MESSAGE
|
|
106
150
|
false
|
|
107
151
|
end
|
|
108
152
|
|
|
109
153
|
def ignored_exception?(ignore_array, exception)
|
|
110
154
|
all_ignored_exceptions = (Array(ignored_exceptions) + Array(ignore_array)).map(&:to_s)
|
|
111
|
-
exception_ancestors = exception.
|
|
155
|
+
exception_ancestors = exception.singleton_class.ancestors.map(&:to_s)
|
|
112
156
|
!(all_ignored_exceptions & exception_ancestors).empty?
|
|
113
157
|
end
|
|
114
158
|
|
|
@@ -118,7 +162,10 @@ module ExceptionNotifier
|
|
|
118
162
|
rescue Exception => e
|
|
119
163
|
raise e if @@testing_mode
|
|
120
164
|
|
|
121
|
-
logger.warn
|
|
165
|
+
logger.warn(
|
|
166
|
+
"An error occurred when sending a notification using '#{notifier_name}' notifier." \
|
|
167
|
+
"#{e.class}: #{e.message}\n#{e.backtrace.join("\n")}"
|
|
168
|
+
)
|
|
122
169
|
false
|
|
123
170
|
end
|
|
124
171
|
|
|
@@ -128,7 +175,15 @@ module ExceptionNotifier
|
|
|
128
175
|
notifier = notifier_class.new(options)
|
|
129
176
|
register_exception_notifier(name, notifier)
|
|
130
177
|
rescue NameError => e
|
|
131
|
-
raise UndefinedNotifierError,
|
|
178
|
+
raise UndefinedNotifierError,
|
|
179
|
+
"No notifier named '#{name}' was found. Please, revise your configuration options. Cause: #{e.message}"
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def from_crawler(env, ignored_crawlers)
|
|
183
|
+
agent = env['HTTP_USER_AGENT']
|
|
184
|
+
Array(ignored_crawlers).any? do |crawler|
|
|
185
|
+
agent =~ Regexp.new(crawler)
|
|
186
|
+
end
|
|
132
187
|
end
|
|
133
188
|
end
|
|
134
189
|
end
|
|
@@ -1,14 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module ExceptionNotification
|
|
2
4
|
module Generators
|
|
3
5
|
class InstallGenerator < Rails::Generators::Base
|
|
4
|
-
desc
|
|
6
|
+
desc 'Creates a ExceptionNotification initializer.'
|
|
5
7
|
|
|
6
|
-
source_root File.expand_path('
|
|
7
|
-
class_option :resque,
|
|
8
|
-
|
|
8
|
+
source_root File.expand_path('templates', __dir__)
|
|
9
|
+
class_option :resque,
|
|
10
|
+
type: :boolean,
|
|
11
|
+
desc: 'Add support for sending notifications when errors occur in Resque jobs.'
|
|
12
|
+
class_option :sidekiq,
|
|
13
|
+
type: :boolean,
|
|
14
|
+
desc: 'Add support for sending notifications when errors occur in Sidekiq jobs.'
|
|
9
15
|
|
|
10
16
|
def copy_initializer
|
|
11
|
-
template 'exception_notification.rb', 'config/initializers/exception_notification.rb'
|
|
17
|
+
template 'exception_notification.rb.erb', 'config/initializers/exception_notification.rb'
|
|
12
18
|
end
|
|
13
19
|
end
|
|
14
20
|
end
|
|
@@ -22,32 +22,34 @@ ExceptionNotification.configure do |config|
|
|
|
22
22
|
# not Rails.env.production?
|
|
23
23
|
# end
|
|
24
24
|
|
|
25
|
+
# Ignore exceptions generated by crawlers
|
|
26
|
+
# config.ignore_crawlers %w{Googlebot bingbot}
|
|
27
|
+
|
|
25
28
|
# Notifiers =================================================================
|
|
26
29
|
|
|
27
30
|
# Email notifier sends notifications by email.
|
|
28
31
|
config.add_notifier :email, {
|
|
29
|
-
:
|
|
30
|
-
:
|
|
31
|
-
:
|
|
32
|
+
email_prefix: '[ERROR] ',
|
|
33
|
+
sender_address: %{"Notifier" <notifier@example.com>},
|
|
34
|
+
exception_recipients: %w{exceptions@example.com}
|
|
32
35
|
}
|
|
33
36
|
|
|
34
37
|
# Campfire notifier sends notifications to your Campfire room. Requires 'tinder' gem.
|
|
35
38
|
# config.add_notifier :campfire, {
|
|
36
|
-
# :
|
|
37
|
-
# :
|
|
38
|
-
# :
|
|
39
|
+
# subdomain: 'my_subdomain',
|
|
40
|
+
# token: 'my_token',
|
|
41
|
+
# room_name: 'my_room'
|
|
39
42
|
# }
|
|
40
43
|
|
|
41
44
|
# HipChat notifier sends notifications to your HipChat room. Requires 'hipchat' gem.
|
|
42
45
|
# config.add_notifier :hipchat, {
|
|
43
|
-
# :
|
|
44
|
-
# :
|
|
46
|
+
# api_token: 'my_token',
|
|
47
|
+
# room_name: 'my_room'
|
|
45
48
|
# }
|
|
46
49
|
|
|
47
50
|
# Webhook notifier sends notifications over HTTP protocol. Requires 'httparty' gem.
|
|
48
51
|
# config.add_notifier :webhook, {
|
|
49
|
-
# :
|
|
50
|
-
# :
|
|
52
|
+
# url: 'http://example.com:5555/hubot/path',
|
|
53
|
+
# http_method: :post
|
|
51
54
|
# }
|
|
52
|
-
|
|
53
55
|
end
|
|
@@ -1,44 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'test_helper'
|
|
2
4
|
|
|
3
5
|
class RackTest < ActiveSupport::TestCase
|
|
4
|
-
|
|
5
6
|
setup do
|
|
6
7
|
@pass_app = Object.new
|
|
7
8
|
@pass_app.stubs(:call).returns([nil, { 'X-Cascade' => 'pass' }, nil])
|
|
8
9
|
|
|
9
10
|
@normal_app = Object.new
|
|
10
|
-
@normal_app.stubs(:call).returns([nil, {
|
|
11
|
+
@normal_app.stubs(:call).returns([nil, {}, nil])
|
|
11
12
|
end
|
|
12
13
|
|
|
13
14
|
teardown do
|
|
14
|
-
ExceptionNotifier.
|
|
15
|
-
ExceptionNotifier.notification_trigger = nil
|
|
15
|
+
ExceptionNotifier.reset_notifiers!
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
-
test
|
|
18
|
+
test 'should ignore "X-Cascade" header by default' do
|
|
19
19
|
ExceptionNotifier.expects(:notify_exception).never
|
|
20
20
|
ExceptionNotification::Rack.new(@pass_app).call({})
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
test
|
|
23
|
+
test 'should notify on "X-Cascade" = "pass" if ignore_cascade_pass option is false' do
|
|
24
24
|
ExceptionNotifier.expects(:notify_exception).once
|
|
25
|
-
ExceptionNotification::Rack.new(@pass_app, :
|
|
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
|
-
ExceptionNotification::Rack.new(@normal_app, notification_trigger:
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
if defined?(Rails) && Rails.respond_to?(:cache)
|
|
41
|
+
test 'should set default cache to Rails cache' do
|
|
42
|
+
ExceptionNotification::Rack.new(@normal_app, error_grouping: true).call({})
|
|
43
|
+
assert_equal Rails.cache, ExceptionNotifier.error_grouping_cache
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
test 'should ignore exceptions with Usar Agent in ignore_crawlers' do
|
|
48
|
+
exception_app = Object.new
|
|
49
|
+
exception_app.stubs(:call).raises(RuntimeError)
|
|
50
|
+
|
|
51
|
+
env = { 'HTTP_USER_AGENT' => 'Mozilla/5.0 (compatible; Crawlerbot/2.1;)' }
|
|
52
|
+
|
|
53
|
+
begin
|
|
54
|
+
ExceptionNotification::Rack.new(exception_app, ignore_crawlers: %w[Crawlerbot]).call(env)
|
|
55
|
+
|
|
56
|
+
flunk
|
|
57
|
+
rescue StandardError
|
|
58
|
+
refute env['exception_notifier.delivered']
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
test 'should ignore exceptions if ignore_if condition is met' do
|
|
63
|
+
exception_app = Object.new
|
|
64
|
+
exception_app.stubs(:call).raises(RuntimeError)
|
|
65
|
+
|
|
66
|
+
env = {}
|
|
67
|
+
|
|
68
|
+
begin
|
|
69
|
+
ExceptionNotification::Rack.new(
|
|
70
|
+
exception_app,
|
|
71
|
+
ignore_if: ->(_env, exception) { exception.is_a? RuntimeError }
|
|
72
|
+
).call(env)
|
|
73
|
+
|
|
74
|
+
flunk
|
|
75
|
+
rescue StandardError
|
|
76
|
+
refute env['exception_notifier.delivered']
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
test 'should ignore exceptions with notifiers that satisfies ignore_notifier_if condition' do
|
|
81
|
+
exception_app = Object.new
|
|
82
|
+
exception_app.stubs(:call).raises(RuntimeError)
|
|
83
|
+
|
|
84
|
+
notifier1_called = notifier2_called = false
|
|
85
|
+
notifier1 = ->(_exception, _options) { notifier1_called = true }
|
|
86
|
+
notifier2 = ->(_exception, _options) { notifier2_called = true }
|
|
87
|
+
|
|
88
|
+
env = {}
|
|
89
|
+
|
|
90
|
+
begin
|
|
91
|
+
ExceptionNotification::Rack.new(
|
|
92
|
+
exception_app,
|
|
93
|
+
ignore_notifier_if: {
|
|
94
|
+
notifier1: ->(_env, exception) { exception.is_a? RuntimeError }
|
|
95
|
+
},
|
|
96
|
+
notifier1: notifier1,
|
|
97
|
+
notifier2: notifier2
|
|
98
|
+
).call(env)
|
|
99
|
+
|
|
100
|
+
flunk
|
|
101
|
+
rescue StandardError
|
|
102
|
+
refute notifier1_called
|
|
103
|
+
assert notifier2_called
|
|
104
|
+
end
|
|
43
105
|
end
|
|
44
106
|
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
|
|
5
|
+
require 'exception_notification/resque'
|
|
6
|
+
require 'resque'
|
|
7
|
+
require 'mock_redis'
|
|
8
|
+
require 'resque/failure/multiple'
|
|
9
|
+
require 'resque/failure/redis'
|
|
10
|
+
|
|
11
|
+
class ResqueTest < ActiveSupport::TestCase
|
|
12
|
+
setup do
|
|
13
|
+
# Resque.redis=() only supports a String or Redis instance in Resque 1.8
|
|
14
|
+
Resque.instance_variable_set(:@redis, MockRedis.new)
|
|
15
|
+
|
|
16
|
+
Resque::Failure::Multiple.classes = [Resque::Failure::Redis, ExceptionNotification::Resque]
|
|
17
|
+
Resque::Failure.backend = Resque::Failure::Multiple
|
|
18
|
+
|
|
19
|
+
@worker = Resque::Worker.new(:jobs)
|
|
20
|
+
# Forking causes issues with Mocha's `.expects`
|
|
21
|
+
@worker.cant_fork = true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
test 'count returns the number of failures' do
|
|
25
|
+
Resque::Job.create(:jobs, BadJob)
|
|
26
|
+
@worker.work(0)
|
|
27
|
+
assert_equal 1, ExceptionNotification::Resque.count
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
test 'notifies exception when job fails' do
|
|
31
|
+
ExceptionNotifier.expects(:notify_exception).with do |ex, opts|
|
|
32
|
+
ex.is_a?(RuntimeError) &&
|
|
33
|
+
ex.message == 'Bad job!' &&
|
|
34
|
+
opts[:data][:resque][:error_class] == 'RuntimeError' &&
|
|
35
|
+
opts[:data][:resque][:error_message] == 'Bad job!' &&
|
|
36
|
+
opts[:data][:resque][:failed_at].present? &&
|
|
37
|
+
opts[:data][:resque][:payload] == {
|
|
38
|
+
'class' => 'ResqueTest::BadJob',
|
|
39
|
+
'args' => []
|
|
40
|
+
} &&
|
|
41
|
+
opts[:data][:resque][:queue] == :jobs &&
|
|
42
|
+
opts[:data][:resque][:worker].present?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
Resque::Job.create(:jobs, BadJob)
|
|
46
|
+
@worker.work(0)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
class BadJob
|
|
50
|
+
def self.perform
|
|
51
|
+
raise 'Bad job!'
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'test_helper'
|
|
4
|
+
require 'dogapi/common'
|
|
5
|
+
require 'dogapi/event'
|
|
6
|
+
|
|
7
|
+
class DatadogNotifierTest < ActiveSupport::TestCase
|
|
8
|
+
def setup
|
|
9
|
+
@client = FakeDatadogClient.new
|
|
10
|
+
@options = {
|
|
11
|
+
client: @client
|
|
12
|
+
}
|
|
13
|
+
@notifier = ExceptionNotifier::DatadogNotifier.new(@options)
|
|
14
|
+
@exception = FakeException.new
|
|
15
|
+
@controller = FakeController.new
|
|
16
|
+
@request = FakeRequest.new
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
test 'should send an event to datadog' do
|
|
20
|
+
fake_event = Dogapi::Event.any_instance
|
|
21
|
+
@client.expects(:emit_event).with(fake_event)
|
|
22
|
+
|
|
23
|
+
@notifier.stubs(:datadog_event).returns(fake_event)
|
|
24
|
+
@notifier.call(@exception)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
test 'should include exception class in event title' do
|
|
28
|
+
event = @notifier.datadog_event(@exception)
|
|
29
|
+
assert_includes event.msg_title, 'FakeException'
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
test 'should include prefix in event title and not append previous events' do
|
|
33
|
+
options = {
|
|
34
|
+
client: @client,
|
|
35
|
+
title_prefix: 'prefix'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
notifier = ExceptionNotifier::DatadogNotifier.new(options)
|
|
39
|
+
event = notifier.datadog_event(@exception)
|
|
40
|
+
assert_equal event.msg_title, 'prefix (DatadogNotifierTest::FakeException) "Fake exception message"'
|
|
41
|
+
|
|
42
|
+
event2 = notifier.datadog_event(@exception)
|
|
43
|
+
assert_equal event2.msg_title, 'prefix (DatadogNotifierTest::FakeException) "Fake exception message"'
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
test 'should include exception message in event title' do
|
|
47
|
+
event = @notifier.datadog_event(@exception)
|
|
48
|
+
assert_includes event.msg_title, 'Fake exception message'
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
test 'should include controller info in event title if controller information is available' do
|
|
52
|
+
event = @notifier.datadog_event(@exception,
|
|
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
|
+
end
|
|
61
|
+
|
|
62
|
+
test 'should include backtrace info in event body' do
|
|
63
|
+
event = @notifier.datadog_event(@exception)
|
|
64
|
+
assert_includes event.msg_text, "backtrace line 1\nbacktrace line 2\nbacktrace line 3"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
test 'should include request info in event body' do
|
|
68
|
+
ActionDispatch::Request.stubs(:new).returns(@request)
|
|
69
|
+
|
|
70
|
+
event = @notifier.datadog_event(@exception,
|
|
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"}'
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
test 'should include tags in event' do
|
|
83
|
+
options = {
|
|
84
|
+
client: @client,
|
|
85
|
+
tags: %w[error production]
|
|
86
|
+
}
|
|
87
|
+
notifier = ExceptionNotifier::DatadogNotifier.new(options)
|
|
88
|
+
event = notifier.datadog_event(@exception)
|
|
89
|
+
assert_equal event.tags, %w[error production]
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
test 'should include event title in event aggregation key' do
|
|
93
|
+
event = @notifier.datadog_event(@exception)
|
|
94
|
+
assert_equal event.aggregation_key, [event.msg_title]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
class FakeDatadogClient
|
|
98
|
+
def emit_event(event); end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
class FakeController
|
|
102
|
+
def controller_name
|
|
103
|
+
'Fake controller'
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def action_name
|
|
107
|
+
'Fake action'
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
class FakeException
|
|
112
|
+
def backtrace
|
|
113
|
+
[
|
|
114
|
+
'backtrace line 1',
|
|
115
|
+
'backtrace line 2',
|
|
116
|
+
'backtrace line 3',
|
|
117
|
+
'backtrace line 4',
|
|
118
|
+
'backtrace line 5'
|
|
119
|
+
]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def message
|
|
123
|
+
'Fake exception message'
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
class FakeRequest
|
|
128
|
+
def url
|
|
129
|
+
'http://localhost:8080'
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def request_method
|
|
133
|
+
'GET'
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def remote_ip
|
|
137
|
+
'127.0.0.1'
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def filtered_parameters
|
|
141
|
+
{
|
|
142
|
+
'param 1' => 'value 1',
|
|
143
|
+
'param 2' => 'value 2'
|
|
144
|
+
}
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def session
|
|
148
|
+
{
|
|
149
|
+
'session_id' => '1234'
|
|
150
|
+
}
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|