exception_notification 4.3.0 → 4.4.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/Appraisals +2 -2
- data/CHANGELOG.rdoc +14 -0
- data/CONTRIBUTING.md +18 -0
- data/Gemfile +1 -1
- data/README.md +64 -935
- data/Rakefile +2 -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 +54 -0
- data/examples/sinatra/Gemfile +6 -6
- data/examples/sinatra/config.ru +1 -1
- data/examples/sinatra/sinatra_app.rb +14 -10
- data/exception_notification.gemspec +27 -22
- data/gemfiles/rails4_0.gemfile +3 -3
- data/gemfiles/rails4_1.gemfile +3 -3
- data/gemfiles/rails4_2.gemfile +3 -3
- data/gemfiles/rails5_0.gemfile +3 -3
- data/gemfiles/rails5_1.gemfile +3 -3
- data/gemfiles/rails5_2.gemfile +7 -0
- data/gemfiles/rails6_0.gemfile +7 -0
- data/lib/exception_notification.rb +1 -0
- data/lib/exception_notification/rack.rb +8 -21
- data/lib/exception_notification/resque.rb +8 -10
- data/lib/exception_notification/sidekiq.rb +8 -12
- data/lib/exception_notification/version.rb +3 -0
- data/lib/exception_notifier.rb +20 -3
- data/lib/exception_notifier/base_notifier.rb +2 -3
- data/lib/exception_notifier/campfire_notifier.rb +12 -13
- data/lib/exception_notifier/datadog_notifier.rb +153 -0
- data/lib/exception_notifier/email_notifier.rb +64 -87
- data/lib/exception_notifier/google_chat_notifier.rb +25 -119
- data/lib/exception_notifier/hipchat_notifier.rb +11 -12
- data/lib/exception_notifier/irc_notifier.rb +32 -30
- data/lib/exception_notifier/mattermost_notifier.rb +47 -140
- data/lib/exception_notifier/modules/backtrace_cleaner.rb +0 -2
- data/lib/exception_notifier/modules/error_grouping.rb +5 -5
- data/lib/exception_notifier/modules/formatter.rb +118 -0
- data/lib/exception_notifier/notifier.rb +5 -6
- data/lib/exception_notifier/slack_notifier.rb +63 -40
- data/lib/exception_notifier/sns_notifier.rb +17 -11
- data/lib/exception_notifier/teams_notifier.rb +58 -44
- 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 +14 -11
- data/lib/generators/exception_notification/install_generator.rb +5 -5
- data/lib/generators/exception_notification/templates/{exception_notification.rb → exception_notification.rb.erb} +13 -11
- data/test/exception_notification/rack_test.rb +27 -11
- data/test/exception_notification/resque_test.rb +52 -0
- data/test/exception_notifier/campfire_notifier_test.rb +42 -42
- data/test/exception_notifier/datadog_notifier_test.rb +151 -0
- data/test/exception_notifier/email_notifier_test.rb +269 -153
- data/test/exception_notifier/google_chat_notifier_test.rb +154 -101
- data/test/exception_notifier/hipchat_notifier_test.rb +78 -81
- data/test/exception_notifier/irc_notifier_test.rb +34 -34
- data/test/exception_notifier/mattermost_notifier_test.rb +164 -67
- data/test/exception_notifier/modules/error_grouping_test.rb +39 -40
- data/test/exception_notifier/modules/formatter_test.rb +150 -0
- data/test/exception_notifier/sidekiq_test.rb +6 -6
- data/test/exception_notifier/slack_notifier_test.rb +61 -60
- data/test/exception_notifier/sns_notifier_test.rb +27 -32
- data/test/exception_notifier/teams_notifier_test.rb +23 -26
- data/test/exception_notifier/webhook_notifier_test.rb +48 -46
- data/test/exception_notifier_test.rb +41 -38
- 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 +11 -14
- metadata +136 -166
- 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.ru +0 -4
- 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/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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<% filtered_env = @request.filtered_env -%>
|
|
2
2
|
<% max = filtered_env.keys.map(&:to_s).max { |a, b| a.length <=> b.length } -%>
|
|
3
3
|
<% filtered_env.keys.map(&:to_s).sort.each do |key| -%>
|
|
4
|
-
* <%= raw safe_encode("%-*s: %s" % [max.length, key, inspect_object(filtered_env[key])]) %>
|
|
4
|
+
* <%= raw safe_encode("%-*s: %s" % [max.length, key, inspect_object(filtered_env[key])]).strip %>
|
|
5
5
|
<% end -%>
|
|
@@ -11,11 +11,11 @@
|
|
|
11
11
|
begin
|
|
12
12
|
summary = render(section).strip
|
|
13
13
|
unless summary.blank?
|
|
14
|
-
title = render("title", :
|
|
14
|
+
title = render("title", title: section).strip
|
|
15
15
|
[title, summary]
|
|
16
16
|
end
|
|
17
17
|
rescue Exception => e
|
|
18
|
-
title = render("title", :
|
|
18
|
+
title = render("title", title: section).strip
|
|
19
19
|
summary = ["ERROR: Failed to generate exception summary:", [e.class.to_s, e.message].join(": "), e.backtrace && e.backtrace.join("\n")].compact.join("\n\n")
|
|
20
20
|
[title, summary]
|
|
21
21
|
end
|
|
@@ -8,12 +8,12 @@
|
|
|
8
8
|
begin
|
|
9
9
|
summary = render(section).strip
|
|
10
10
|
unless summary.blank?
|
|
11
|
-
title = render("title", :
|
|
11
|
+
title = render("title", title: section).strip
|
|
12
12
|
"#{title}\n\n#{summary.gsub(/^/, " ")}\n\n"
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
rescue Exception => e
|
|
16
|
-
title = render("title", :
|
|
16
|
+
title = render("title", title: section).strip
|
|
17
17
|
summary = ["ERROR: Failed to generate exception summary:", [e.class.to_s, e.message].join(": "), e.backtrace && e.backtrace.join("\n")].compact.join("\n\n")
|
|
18
18
|
|
|
19
19
|
[title, summary.gsub(/^/, " "), nil].join("\n\n")
|
|
@@ -3,13 +3,12 @@ require 'active_support/core_ext/time'
|
|
|
3
3
|
|
|
4
4
|
module ExceptionNotifier
|
|
5
5
|
class WebhookNotifier < BaseNotifier
|
|
6
|
-
|
|
7
6
|
def initialize(options)
|
|
8
7
|
super
|
|
9
8
|
@default_options = options
|
|
10
9
|
end
|
|
11
10
|
|
|
12
|
-
def call(exception, options={})
|
|
11
|
+
def call(exception, options = {})
|
|
13
12
|
env = options[:env]
|
|
14
13
|
|
|
15
14
|
options = options.reverse_merge(@default_options)
|
|
@@ -18,23 +17,27 @@ module ExceptionNotifier
|
|
|
18
17
|
|
|
19
18
|
options[:body] ||= {}
|
|
20
19
|
options[:body][:server] = Socket.gethostname
|
|
21
|
-
options[:body][:process] =
|
|
20
|
+
options[:body][:process] = $PROCESS_ID
|
|
22
21
|
if defined?(Rails) && Rails.respond_to?(:root)
|
|
23
22
|
options[:body][:rails_root] = Rails.root
|
|
24
23
|
end
|
|
25
|
-
options[:body][:exception] = {
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
module ExceptionNotification
|
|
2
2
|
module Generators
|
|
3
3
|
class InstallGenerator < Rails::Generators::Base
|
|
4
|
-
desc
|
|
4
|
+
desc 'Creates a ExceptionNotification initializer.'
|
|
5
5
|
|
|
6
|
-
source_root File.expand_path('
|
|
7
|
-
class_option :resque, :
|
|
8
|
-
class_option :sidekiq, :
|
|
6
|
+
source_root File.expand_path('templates', __dir__)
|
|
7
|
+
class_option :resque, type: :boolean, desc: 'Add support for sending notifications when errors occur in Resque jobs.'
|
|
8
|
+
class_option :sidekiq, type: :boolean, desc: 'Add support for sending notifications when errors occur in Sidekiq jobs.'
|
|
9
9
|
|
|
10
10
|
def copy_initializer
|
|
11
|
-
template 'exception_notification.rb', 'config/initializers/exception_notification.rb'
|
|
11
|
+
template 'exception_notification.rb.erb', 'config/initializers/exception_notification.rb'
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
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,13 +1,12 @@
|
|
|
1
1
|
require 'test_helper'
|
|
2
2
|
|
|
3
3
|
class RackTest < ActiveSupport::TestCase
|
|
4
|
-
|
|
5
4
|
setup do
|
|
6
5
|
@pass_app = Object.new
|
|
7
6
|
@pass_app.stubs(:call).returns([nil, { 'X-Cascade' => 'pass' }, nil])
|
|
8
7
|
|
|
9
8
|
@normal_app = Object.new
|
|
10
|
-
@normal_app.stubs(:call).returns([nil, {
|
|
9
|
+
@normal_app.stubs(:call).returns([nil, {}, nil])
|
|
11
10
|
end
|
|
12
11
|
|
|
13
12
|
teardown do
|
|
@@ -15,30 +14,47 @@ class RackTest < ActiveSupport::TestCase
|
|
|
15
14
|
ExceptionNotifier.notification_trigger = nil
|
|
16
15
|
end
|
|
17
16
|
|
|
18
|
-
test
|
|
17
|
+
test 'should ignore "X-Cascade" header by default' do
|
|
19
18
|
ExceptionNotifier.expects(:notify_exception).never
|
|
20
19
|
ExceptionNotification::Rack.new(@pass_app).call({})
|
|
21
20
|
end
|
|
22
21
|
|
|
23
|
-
test
|
|
22
|
+
test 'should notify on "X-Cascade" = "pass" if ignore_cascade_pass option is false' do
|
|
24
23
|
ExceptionNotifier.expects(:notify_exception).once
|
|
25
|
-
ExceptionNotification::Rack.new(@pass_app, :
|
|
24
|
+
ExceptionNotification::Rack.new(@pass_app, ignore_cascade_pass: false).call({})
|
|
26
25
|
end
|
|
27
26
|
|
|
28
|
-
test
|
|
27
|
+
test 'should assign error_grouping if error_grouping is specified' do
|
|
29
28
|
refute ExceptionNotifier.error_grouping
|
|
30
29
|
ExceptionNotification::Rack.new(@normal_app, error_grouping: true).call({})
|
|
31
30
|
assert ExceptionNotifier.error_grouping
|
|
32
31
|
end
|
|
33
32
|
|
|
34
|
-
test
|
|
33
|
+
test 'should assign notification_trigger if notification_trigger is specified' do
|
|
35
34
|
assert_nil ExceptionNotifier.notification_trigger
|
|
36
|
-
ExceptionNotification::Rack.new(@normal_app, notification_trigger:
|
|
35
|
+
ExceptionNotification::Rack.new(@normal_app, notification_trigger: ->(_i) { true }).call({})
|
|
37
36
|
assert_respond_to ExceptionNotifier.notification_trigger, :call
|
|
38
37
|
end
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
if defined?(Rails) && Rails.respond_to?(:cache)
|
|
40
|
+
test 'should set default cache to Rails cache' do
|
|
41
|
+
ExceptionNotification::Rack.new(@normal_app, error_grouping: true).call({})
|
|
42
|
+
assert_equal Rails.cache, ExceptionNotifier.error_grouping_cache
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
test 'should ignore exceptions with Usar Agent in ignore_crawlers' do
|
|
47
|
+
exception_app = Object.new
|
|
48
|
+
exception_app.stubs(:call).raises(RuntimeError)
|
|
49
|
+
|
|
50
|
+
env = { 'HTTP_USER_AGENT' => 'Mozilla/5.0 (compatible; Crawlerbot/2.1;)' }
|
|
51
|
+
|
|
52
|
+
begin
|
|
53
|
+
ExceptionNotification::Rack.new(exception_app, ignore_crawlers: %w[Crawlerbot]).call(env)
|
|
54
|
+
|
|
55
|
+
flunk
|
|
56
|
+
rescue StandardError
|
|
57
|
+
refute env['exception_notifier.delivered']
|
|
58
|
+
end
|
|
43
59
|
end
|
|
44
60
|
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
|
|
3
|
+
require 'exception_notification/resque'
|
|
4
|
+
require 'resque'
|
|
5
|
+
require 'mock_redis'
|
|
6
|
+
require 'resque/failure/multiple'
|
|
7
|
+
require 'resque/failure/redis'
|
|
8
|
+
|
|
9
|
+
class ResqueTest < ActiveSupport::TestCase
|
|
10
|
+
setup do
|
|
11
|
+
# Resque.redis=() only supports a String or Redis instance in Resque 1.8
|
|
12
|
+
Resque.instance_variable_set(:@redis, MockRedis.new)
|
|
13
|
+
|
|
14
|
+
Resque::Failure::Multiple.classes = [Resque::Failure::Redis, ExceptionNotification::Resque]
|
|
15
|
+
Resque::Failure.backend = Resque::Failure::Multiple
|
|
16
|
+
|
|
17
|
+
@worker = Resque::Worker.new(:jobs)
|
|
18
|
+
# Forking causes issues with Mocha's `.expects`
|
|
19
|
+
@worker.cant_fork = true
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
test 'count returns the number of failures' do
|
|
23
|
+
Resque::Job.create(:jobs, BadJob)
|
|
24
|
+
@worker.work(0)
|
|
25
|
+
assert_equal 1, ExceptionNotification::Resque.count
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
test 'notifies exception when job fails' do
|
|
29
|
+
ExceptionNotifier.expects(:notify_exception).with do |ex, opts|
|
|
30
|
+
ex.is_a?(RuntimeError) &&
|
|
31
|
+
ex.message == 'Bad job!' &&
|
|
32
|
+
opts[:data][:resque][:error_class] == 'RuntimeError' &&
|
|
33
|
+
opts[:data][:resque][:error_message] == 'Bad job!' &&
|
|
34
|
+
opts[:data][:resque][:failed_at].present? &&
|
|
35
|
+
opts[:data][:resque][:payload] == {
|
|
36
|
+
'class' => 'ResqueTest::BadJob',
|
|
37
|
+
'args' => []
|
|
38
|
+
} &&
|
|
39
|
+
opts[:data][:resque][:queue] == :jobs &&
|
|
40
|
+
opts[:data][:resque][:worker].present?
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
Resque::Job.create(:jobs, BadJob)
|
|
44
|
+
@worker.work(0)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class BadJob
|
|
48
|
+
def self.perform
|
|
49
|
+
raise 'Bad job!'
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -8,43 +8,42 @@ silence_warnings do
|
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
class CampfireNotifierTest < ActiveSupport::TestCase
|
|
11
|
-
|
|
12
|
-
test "should send campfire notification if properly configured" do
|
|
11
|
+
test 'should send campfire notification if properly configured' do
|
|
13
12
|
ExceptionNotifier::CampfireNotifier.stubs(:new).returns(Object.new)
|
|
14
|
-
campfire = ExceptionNotifier::CampfireNotifier.new(
|
|
13
|
+
campfire = ExceptionNotifier::CampfireNotifier.new(subdomain: 'test', token: 'test_token', room_name: 'test_room')
|
|
15
14
|
campfire.stubs(:call).returns(fake_notification)
|
|
16
15
|
notif = campfire.call(fake_exception)
|
|
17
16
|
|
|
18
17
|
assert !notif[:message].empty?
|
|
19
18
|
assert_equal notif[:message][:type], 'PasteMessage'
|
|
20
|
-
assert_includes notif[:message][:body],
|
|
21
|
-
assert_includes notif[:message][:body],
|
|
22
|
-
assert_includes notif[:message][:body],
|
|
19
|
+
assert_includes notif[:message][:body], 'A new exception occurred:'
|
|
20
|
+
assert_includes notif[:message][:body], 'divided by 0'
|
|
21
|
+
assert_includes notif[:message][:body], '/exception_notification/test/campfire_test.rb:45'
|
|
23
22
|
end
|
|
24
23
|
|
|
25
|
-
test
|
|
24
|
+
test 'should send campfire notification without backtrace info if properly configured' do
|
|
26
25
|
ExceptionNotifier::CampfireNotifier.stubs(:new).returns(Object.new)
|
|
27
|
-
campfire = ExceptionNotifier::CampfireNotifier.new(
|
|
26
|
+
campfire = ExceptionNotifier::CampfireNotifier.new(subdomain: 'test', token: 'test_token', room_name: 'test_room')
|
|
28
27
|
campfire.stubs(:call).returns(fake_notification_without_backtrace)
|
|
29
28
|
notif = campfire.call(fake_exception_without_backtrace)
|
|
30
29
|
|
|
31
30
|
assert !notif[:message].empty?
|
|
32
31
|
assert_equal notif[:message][:type], 'PasteMessage'
|
|
33
|
-
assert_includes notif[:message][:body],
|
|
34
|
-
assert_includes notif[:message][:body],
|
|
32
|
+
assert_includes notif[:message][:body], 'A new exception occurred:'
|
|
33
|
+
assert_includes notif[:message][:body], 'my custom error'
|
|
35
34
|
end
|
|
36
35
|
|
|
37
|
-
test
|
|
38
|
-
wrong_params = {:
|
|
39
|
-
Tinder::Campfire.stubs(:new).with('test',
|
|
36
|
+
test 'should not send campfire notification if badly configured' do
|
|
37
|
+
wrong_params = { subdomain: 'test', token: 'bad_token', room_name: 'test_room' }
|
|
38
|
+
Tinder::Campfire.stubs(:new).with('test', token: 'bad_token').returns(nil)
|
|
40
39
|
campfire = ExceptionNotifier::CampfireNotifier.new(wrong_params)
|
|
41
40
|
|
|
42
41
|
assert_nil campfire.room
|
|
43
42
|
assert_nil campfire.call(fake_exception)
|
|
44
43
|
end
|
|
45
44
|
|
|
46
|
-
test
|
|
47
|
-
wrong_params
|
|
45
|
+
test 'should not send campfire notification if config attr missing' do
|
|
46
|
+
wrong_params = { subdomain: 'test', room_name: 'test_room' }
|
|
48
47
|
Tinder::Campfire.stubs(:new).with('test', {}).returns(nil)
|
|
49
48
|
campfire = ExceptionNotifier::CampfireNotifier.new(wrong_params)
|
|
50
49
|
|
|
@@ -52,35 +51,34 @@ class CampfireNotifierTest < ActiveSupport::TestCase
|
|
|
52
51
|
assert_nil campfire.call(fake_exception)
|
|
53
52
|
end
|
|
54
53
|
|
|
55
|
-
test
|
|
54
|
+
test 'should send the new exception message if no :accumulated_errors_count option' do
|
|
56
55
|
campfire = ExceptionNotifier::CampfireNotifier.new({})
|
|
57
56
|
campfire.stubs(:active?).returns(true)
|
|
58
|
-
campfire.expects(:send_notice).with{ |_, _, message| message.start_with?(
|
|
57
|
+
campfire.expects(:send_notice).with { |_, _, message| message.start_with?('A new exception occurred') }.once
|
|
59
58
|
campfire.call(fake_exception)
|
|
60
59
|
end
|
|
61
60
|
|
|
62
|
-
test
|
|
61
|
+
test 'shoud send the exception message if :accumulated_errors_count option greater than 1' do
|
|
63
62
|
campfire = ExceptionNotifier::CampfireNotifier.new({})
|
|
64
63
|
campfire.stubs(:active?).returns(true)
|
|
65
|
-
campfire.expects(:send_notice).with{ |_, _, message| message.start_with?(
|
|
64
|
+
campfire.expects(:send_notice).with { |_, _, message| message.start_with?('The exception occurred 3 times:') }.once
|
|
66
65
|
campfire.call(fake_exception, accumulated_errors_count: 3)
|
|
67
66
|
end
|
|
68
67
|
|
|
69
|
-
test
|
|
70
|
-
pre_callback_called
|
|
68
|
+
test 'should call pre/post_callback if specified' do
|
|
69
|
+
pre_callback_called = 0
|
|
70
|
+
post_callback_called = 0
|
|
71
71
|
Tinder::Campfire.stubs(:new).returns(Object.new)
|
|
72
72
|
|
|
73
73
|
campfire = ExceptionNotifier::CampfireNotifier.new(
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
post_callback_called += 1
|
|
83
|
-
}
|
|
74
|
+
subdomain: 'test',
|
|
75
|
+
token: 'test_token',
|
|
76
|
+
room_name: 'test_room',
|
|
77
|
+
pre_callback: proc { |_opts, _notifier, _backtrace, _message, _message_opts|
|
|
78
|
+
pre_callback_called += 1
|
|
79
|
+
},
|
|
80
|
+
post_callback: proc { |_opts, _notifier, _backtrace, _message, _message_opts|
|
|
81
|
+
post_callback_called += 1
|
|
84
82
|
}
|
|
85
83
|
)
|
|
86
84
|
campfire.room = Object.new
|
|
@@ -93,24 +91,26 @@ class CampfireNotifierTest < ActiveSupport::TestCase
|
|
|
93
91
|
private
|
|
94
92
|
|
|
95
93
|
def fake_notification
|
|
96
|
-
{
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
{
|
|
95
|
+
message: {
|
|
96
|
+
type: 'PasteMessage',
|
|
97
|
+
body: "A new exception occurred: 'divided by 0' on '/Users/sebastian/exception_notification/test/campfire_test.rb:45:in `/'"
|
|
98
|
+
}
|
|
99
99
|
}
|
|
100
100
|
end
|
|
101
101
|
|
|
102
102
|
def fake_exception
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
e
|
|
107
|
-
end
|
|
103
|
+
5 / 0
|
|
104
|
+
rescue StandardError => e
|
|
105
|
+
e
|
|
108
106
|
end
|
|
109
107
|
|
|
110
108
|
def fake_notification_without_backtrace
|
|
111
|
-
{
|
|
112
|
-
|
|
113
|
-
|
|
109
|
+
{
|
|
110
|
+
message: {
|
|
111
|
+
type: 'PasteMessage',
|
|
112
|
+
body: "A new exception occurred: 'my custom error'"
|
|
113
|
+
}
|
|
114
114
|
}
|
|
115
115
|
end
|
|
116
116
|
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
require 'test_helper'
|
|
2
|
+
require 'dogapi/common'
|
|
3
|
+
require 'dogapi/event'
|
|
4
|
+
|
|
5
|
+
class DatadogNotifierTest < ActiveSupport::TestCase
|
|
6
|
+
def setup
|
|
7
|
+
@client = FakeDatadogClient.new
|
|
8
|
+
@options = {
|
|
9
|
+
client: @client
|
|
10
|
+
}
|
|
11
|
+
@notifier = ExceptionNotifier::DatadogNotifier.new(@options)
|
|
12
|
+
@exception = FakeException.new
|
|
13
|
+
@controller = FakeController.new
|
|
14
|
+
@request = FakeRequest.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
test 'should send an event to datadog' do
|
|
18
|
+
fake_event = Dogapi::Event.any_instance
|
|
19
|
+
@client.expects(:emit_event).with(fake_event)
|
|
20
|
+
|
|
21
|
+
@notifier.stubs(:datadog_event).returns(fake_event)
|
|
22
|
+
@notifier.call(@exception)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
test 'should include exception class in event title' do
|
|
26
|
+
event = @notifier.datadog_event(@exception)
|
|
27
|
+
assert_includes event.msg_title, 'FakeException'
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
test 'should include prefix in event title and not append previous events' do
|
|
31
|
+
options = {
|
|
32
|
+
client: @client,
|
|
33
|
+
title_prefix: 'prefix'
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
notifier = ExceptionNotifier::DatadogNotifier.new(options)
|
|
37
|
+
event = notifier.datadog_event(@exception)
|
|
38
|
+
assert_equal event.msg_title, 'prefix (DatadogNotifierTest::FakeException) "Fake exception message"'
|
|
39
|
+
|
|
40
|
+
event2 = notifier.datadog_event(@exception)
|
|
41
|
+
assert_equal event2.msg_title, 'prefix (DatadogNotifierTest::FakeException) "Fake exception message"'
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
test 'should include exception message in event title' do
|
|
45
|
+
event = @notifier.datadog_event(@exception)
|
|
46
|
+
assert_includes event.msg_title, 'Fake exception message'
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
test 'should include controller info in event title if controller information is available' do
|
|
50
|
+
event = @notifier.datadog_event(@exception,
|
|
51
|
+
env: {
|
|
52
|
+
'action_controller.instance' => @controller,
|
|
53
|
+
'REQUEST_METHOD' => 'GET',
|
|
54
|
+
'rack.input' => ''
|
|
55
|
+
})
|
|
56
|
+
assert_includes event.msg_title, 'Fake controller'
|
|
57
|
+
assert_includes event.msg_title, 'Fake action'
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
test 'should include backtrace info in event body' do
|
|
61
|
+
event = @notifier.datadog_event(@exception)
|
|
62
|
+
assert_includes event.msg_text, "backtrace line 1\nbacktrace line 2\nbacktrace line 3"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
test 'should include request info in event body' do
|
|
66
|
+
ActionDispatch::Request.stubs(:new).returns(@request)
|
|
67
|
+
|
|
68
|
+
event = @notifier.datadog_event(@exception,
|
|
69
|
+
env: {
|
|
70
|
+
'action_controller.instance' => @controller,
|
|
71
|
+
'REQUEST_METHOD' => 'GET',
|
|
72
|
+
'rack.input' => ''
|
|
73
|
+
})
|
|
74
|
+
assert_includes event.msg_text, 'http://localhost:8080'
|
|
75
|
+
assert_includes event.msg_text, 'GET'
|
|
76
|
+
assert_includes event.msg_text, '127.0.0.1'
|
|
77
|
+
assert_includes event.msg_text, '{"param 1"=>"value 1", "param 2"=>"value 2"}'
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
test 'should include tags in event' do
|
|
81
|
+
options = {
|
|
82
|
+
client: @client,
|
|
83
|
+
tags: %w[error production]
|
|
84
|
+
}
|
|
85
|
+
notifier = ExceptionNotifier::DatadogNotifier.new(options)
|
|
86
|
+
event = notifier.datadog_event(@exception)
|
|
87
|
+
assert_equal event.tags, %w[error production]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
test 'should include event title in event aggregation key' do
|
|
91
|
+
event = @notifier.datadog_event(@exception)
|
|
92
|
+
assert_equal event.aggregation_key, [event.msg_title]
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
class FakeDatadogClient
|
|
96
|
+
def emit_event(event); end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
class FakeController
|
|
100
|
+
def controller_name
|
|
101
|
+
'Fake controller'
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def action_name
|
|
105
|
+
'Fake action'
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
class FakeException
|
|
110
|
+
def backtrace
|
|
111
|
+
[
|
|
112
|
+
'backtrace line 1',
|
|
113
|
+
'backtrace line 2',
|
|
114
|
+
'backtrace line 3',
|
|
115
|
+
'backtrace line 4',
|
|
116
|
+
'backtrace line 5'
|
|
117
|
+
]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def message
|
|
121
|
+
'Fake exception message'
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
class FakeRequest
|
|
126
|
+
def url
|
|
127
|
+
'http://localhost:8080'
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def request_method
|
|
131
|
+
'GET'
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def remote_ip
|
|
135
|
+
'127.0.0.1'
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def filtered_parameters
|
|
139
|
+
{
|
|
140
|
+
'param 1' => 'value 1',
|
|
141
|
+
'param 2' => 'value 2'
|
|
142
|
+
}
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def session
|
|
146
|
+
{
|
|
147
|
+
'session_id' => '1234'
|
|
148
|
+
}
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|